<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>显影之尘</title>
    <description>博客通天下，淡墨书豪辞</description>
    <link>http://www.tangrui.net/</link>
    <atom:link href="http://www.tangrui.net/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Mon, 12 Mar 2018 14:58:20 +0000</pubDate>
    <lastBuildDate>Mon, 12 Mar 2018 14:58:20 +0000</lastBuildDate>
    <generator>Jekyll v3.6.2</generator>
    
      <item>
        <title>精英的游戏</title>
        <description>&lt;p&gt;这次演讲的灵感来自于最近非常火爆的一部美剧《权利的游戏》。&lt;/p&gt;

&lt;p&gt;熟悉剧情的朋友应该了解，这是一部出奇的以死主角著称的影视作品，【幻灯片】当你觉得某个角色能够力挽狂澜，扭转战局的时候，他却戛然在一出最不可思议的阴谋中领了便当。没有能够让吟游歌手写下不朽篇章的伟大战役，也没有能够另族人纪念其丰功伟绩的英勇就义，【幻灯片】有的只是“卡色特梅的雨季”留给观众的无限震撼与遗憾。正应了片中经常出现的一句对白：【幻灯片】Valar morghulis，凡人皆有一死。&lt;/p&gt;

&lt;p&gt;不过历史从来就没有主角，有的只是精英跟伟人。与其说是“权利的游戏”不如说是“精英的角力”。看看那些能够真正存活到收官时刻的人物，也就不难理解真正意义上的精英所需要具备的潜质了【幻灯片】。&lt;/p&gt;

&lt;p&gt;解决重大问题是精英人才的必要条件。这一条件暗含了两个重要特征：其一是主观性——即不仅要善于而且敢于挑战这样的问题；其二就是客观性——“重大”不仅仅是体量上的庞大，更是困难程度上的巨大。回到剧中，【幻灯片】琼恩·雪诺面对塞外异鬼的侵袭，在不顾守夜人兄弟反对的情形下，毅然让野人穿越了长城。这需要无比的勇气和胆识，因为在守夜人的历史上从来都是与野人为敌的；同时野人又是一群目中无法、自由散漫、生性好斗且不服管制的自由民，让他们与守夜人的军队乃至七大王国上下的将士并肩作战，其难度也是可想而知的。【幻灯片】也正因此他才“收获”了誓言兄弟们的报复，横死于利刃之下。也从此摆脱了誓言的束缚，守望于斯结束。倘若都能如临大敌般的对待每一个遭遇到的问题，又能破釜沉舟似的让自己不得不去面对它，或许便有可能绝处逢生。任正非在有关人才的论述中说过这样的观点：【幻灯片】假设一个新研究项目能够做出来，华为就获得了天才；假设一个新研究项目做不出来华为就得到了人才。项目失败的研究人员一定可以更好的总结过去，不重复犯同样的错误，这正是公司所要得到的人才。&lt;/p&gt;

&lt;p&gt;敢于解决重大问题，便迈开了通往精英之路的第一步。但倘若一直都在固步自封的解决着自己擅长的重大问题，也并不能在这条路上前进得更远。我们需要进入到下一个层次，即扩大你在行业中的影响力。解决过那么多的重大问题，必然会有一些经验、心得与体会，而你与行业之间的距离可能并不在于专业技能，而是相差一个舞台和一点点信念。如果有能力和机会登上 TED 的讲台，人生可能会从此不同，但这并非人人都能够做得到，因此这是别人的舞台，华丽却遥不可及。但是我们每个人都能够创建自己的开源项目，撰写自己的技术博客，打造自己的微信公众号，甚至录制一堂自己的公开课。只要你肯做，这些都是近在咫尺的舞台。龙母丹妮莉丝·坦格利安在回顾自己成长过程中历经的各种磨难时如是说：【幻灯片】……那些让我坚持住没有倒下的，既不是对神的信仰，也不是对神话和传说的迷信，而是对我自己的信念。【幻灯片】熟悉前端开发的小伙伴应该都认得尤雨溪——【幻灯片】Vue.js 的作者，【幻灯片】一个出自国人之手的开源框架可以比肩 Facebook 的 ReactJS，我想这其中的影响力便不言而喻了吧。&lt;/p&gt;

&lt;p&gt;至此，我们已经可以声称自己是精英中的一员了，但“精英的游戏”到这里远没有结束。那些所扩大的行业影响力，可以来自于蚕食他人的成果与流量，也可以来自于扩大的市场与环境，前者是零和的，而后者是共赢的。【幻灯片】在权利的游戏中，你不当赢家，就只有死路一条，没有中间地带，而精英的游戏却有可能是双赢的，前提是你得足够优秀。伸手摸一摸自己口袋里的手机：面对苹果，诺基亚的结局是被完全取代；同样是面对苹果，谷歌却可以让用户再买一部手机。足够优秀，足够强大，足够领先同行，就会有更大的市场等待开拓，收获也会颇丰。这期间可能会遇上更多的问题，有更多值得分享的话题。脚踏实地，不务空名，精英游戏的角力中就永远会有你的一席之地。【幻灯片】&lt;/p&gt;
</description>
        <pubDate>Sun, 06 Aug 2017 01:07:00 +0000</pubDate>
        <link>http://www.tangrui.net/2017/game-of-elites.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2017/game-of-elites.html</guid>
        
        <category>演讲</category>
        
        
        <category>其他</category>
        
      </item>
    
      <item>
        <title>在 CentOS 6 上部署 Cloudera Manager 集群</title>
        <description>&lt;h1 id=&quot;一准备工作&quot;&gt;一、准备工作&lt;/h1&gt;

&lt;h2 id=&quot;11系统权限要求&quot;&gt;1.1、系统权限要求&lt;/h2&gt;

&lt;p&gt;最简单的情况是能够以 root 身份登录集群中的所有服务器，但是出于安全考虑，多数情况下的服务器配置是不允许这么操作的，因此需要系统能支持以无密码的方式使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo&lt;/code&gt; 执行如下命令：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;yum&lt;/li&gt;
  &lt;li&gt;sed&lt;/li&gt;
  &lt;li&gt;service&lt;/li&gt;
  &lt;li&gt;chkconfig&lt;/li&gt;
  &lt;li&gt;id&lt;/li&gt;
  &lt;li&gt;rm&lt;/li&gt;
  &lt;li&gt;mv&lt;/li&gt;
  &lt;li&gt;chown&lt;/li&gt;
  &lt;li&gt;install&lt;/li&gt;
  &lt;li&gt;hostname&lt;/li&gt;
  &lt;li&gt;vim&lt;/li&gt;
  &lt;li&gt;setenforce&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;配置方法是在 &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/sudoers&lt;/code&gt; 文件中添加以下内容：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;tangrui             ALL=(ALL)       NOPASSWD: ALL
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;该配置允许 tangrui 用户以无密码的 &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo&lt;/code&gt; 命令执行任意程序，如果想要配置白名单，可以将最后的 &lt;code class=&quot;highlighter-rouge&quot;&gt;ALL&lt;/code&gt; 修改为对应的命令即可。&lt;/p&gt;

&lt;h2 id=&quot;12升级系统&quot;&gt;1.2、升级系统&lt;/h2&gt;

&lt;p&gt;将系统升级到最新版本：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; upgrade
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;13安装-wget&quot;&gt;1.3、安装 wget&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum install &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; wget
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;14安装并配置-ntp-服务&quot;&gt;1.4、安装并配置 NTP 服务&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum install &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; ntp
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;chkconfig ntpd on
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;service ntpd start
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;ntpdate &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; pool.ntp.org
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;15禁用-selinux&quot;&gt;1.5、禁用 SELinux&lt;/h2&gt;

&lt;p&gt;使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;getenforce&lt;/code&gt; 命令检查 SELinux 是否启用，如果不是 Disabled 状态，可以使用如下命令关闭：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;setenforce 0
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;16配置防火墙&quot;&gt;1.6、配置防火墙&lt;/h2&gt;

&lt;p&gt;配置防火墙，以允许集群间的节点主机可以相互通讯。&lt;/p&gt;

&lt;h2 id=&quot;17配置域名解析&quot;&gt;1.7、配置域名解析&lt;/h2&gt;

&lt;p&gt;修改每个节点上的 &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/hosts&lt;/code&gt; 文件，以确保彼此之间能够通过域名解析到正确的 IP 地址，参考如下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;127.0.0.1 localhost.localdomain localhost
192.168.1.1 cluster-01.example.com cluster-01
192.168.1.2 cluster-02.example.com cluster-02
192.168.1.3 cluster-03.example.com cluster-03
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;em&gt;注：小规模集群可以采用修改 hosts 文件的方式实现域名解析，对于大规模集群而言，最好使用 DNS。具体 DNS 的配置方式请参考其他文档。&lt;/em&gt;&lt;/p&gt;

&lt;h2 id=&quot;18安装-jdk&quot;&gt;1.8、安装 JDK&lt;/h2&gt;

&lt;p&gt;安装 JDK 1.7，要确保在系统全局能够访问到 &lt;code class=&quot;highlighter-rouge&quot;&gt;java&lt;/code&gt; 命令，最好使用 rpm 包进行安装。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;注：如果系统自带安装有 OpenJDK，请将其卸载。&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;rpm &lt;span class=&quot;nt&quot;&gt;-aq&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-i&lt;/span&gt; jdk
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;根据该命令输出的结果，使用 &lt;code class=&quot;highlighter-rouge&quot;&gt;sudo yum remove&lt;/code&gt; 方法逐个删除已安装的 OpenJDK。&lt;/p&gt;

&lt;h2 id=&quot;19参考文档&quot;&gt;1.9、参考文档&lt;/h2&gt;

&lt;p&gt;CDH 5 和 Cloudera Manager 5 需求并支持的版本，请参考文档：&lt;a href=&quot;https://www.cloudera.com/documentation/enterprise/release-notes/topics/rn_consolidated_pcm.html&quot;&gt;CDH 5 and Cloudera Manager 5 Requirements and Supported Versions&lt;/a&gt;&lt;/p&gt;

&lt;h1 id=&quot;二系统规划&quot;&gt;二、系统规划&lt;/h1&gt;

&lt;h2 id=&quot;21集群规划&quot;&gt;2.1、集群规划&lt;/h2&gt;

&lt;p&gt;集群规划请参考如下文档：&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.cloudera.com/documentation/enterprise/latest/topics/cm_ig_host_allocations.html&quot;&gt;Cluster Hosts and Role Assignments&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.cloudera.com/documentation/enterprise/latest/topics/cm_ig_installing_configuring_dbs.html&quot;&gt;Cloudera Manager and Managed Service Datastores&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;22服务端口&quot;&gt;2.2、服务端口&lt;/h2&gt;

&lt;p&gt;各服务所需要使用的端口，请参考 Cloudera 的官方文档 &lt;a href=&quot;https://www.cloudera.com/documentation/enterprise/latest/topics/cm_ig_ports.html&quot;&gt;Ports&lt;/a&gt; ，需要据此配置防火墙，以便需要的外部端口能够被集群以外的机器访问。&lt;/p&gt;

&lt;h1 id=&quot;三安装-cloudera-manager-基础组件&quot;&gt;三、安装 Cloudera Manager 基础组件&lt;/h1&gt;

&lt;h2 id=&quot;31添加-cloudera-manager-源&quot;&gt;3.1、添加 Cloudera Manager 源&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;注：需要在每个节点都添加此源。&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd&lt;/span&gt; /etc/yum.repos.d
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;wget http://archive.cloudera.com/cm5/redhat/6/x86_64/cm/cloudera-manager.repo
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;32安装-cloudera-manager-server&quot;&gt;3.2、安装 Cloudera Manager Server&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;注：只需要在一个节点上安装此组件。&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum install &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; cloudera-manager-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;33安装-cloudera-manager-daemons-和-cloudera-manager-agent&quot;&gt;3.3、安装 Cloudera Manager Daemons 和 Cloudera Manager Agent&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;注：需要在每个节点上都安装此组件，此步骤可以省略，但是为了让后面运行 Cloudera Manager 管理界面的时候更快速，可以预先把这些比较大的组件安装好。&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum install &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; cloudera-manager-daemons cloudera-manager-agent
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;四数据库配置&quot;&gt;四、数据库配置&lt;/h1&gt;

&lt;h2 id=&quot;41安装-mysql-服务器&quot;&gt;4.1、安装 MySQL 服务器&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;注：只需要在一个节点上安装此服务，也可以使用已有服务。&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;yum install &lt;span class=&quot;nt&quot;&gt;-y&lt;/span&gt; mysql-server
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;chkconfig mysqld on
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;service mysqld start
&lt;span class=&quot;c&quot;&gt;# 在运行以下命令时，不要禁止 root 用户远程登录 MySQL 服务器&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;mysql_secure_installation
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;倘若不小心在运行 &lt;code class=&quot;highlighter-rouge&quot;&gt;mysql_secure_installation&lt;/code&gt; 命令的时候禁止了 root 用户远程登录 MySQL 服务器，以下命令可以重新开启：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mysql -u root -p
mysql&amp;gt; GRANT ALL PRIVILEGES ON *.* TO 'root'@'%' IDENTIFIED BY '&amp;lt;root_password&amp;gt;' WITH GRANT OPTION;
mysql&amp;gt; FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;42准备-cloudera-manager-server-数据库&quot;&gt;4.2、准备 Cloudera Manager Server 数据库&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;注：有关各数据库的容量规划，请参考文档：&lt;a href=&quot;https://www.cloudera.com/documentation/enterprise/latest/topics/cm_ig_installing_configuring_dbs.html#cmig_topic_5_1&quot;&gt;Cloudera Manager and Managed Service Datastores&lt;/a&gt;。&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;在安装有 Cloudera Manager Server 的节点上运行如下命令：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo&lt;/span&gt; /usr/share/cmf/schema/scm_prepare_database.sh mysql &lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt; &amp;lt;mysql_host&amp;gt; &lt;span class=&quot;nt&quot;&gt;-u&lt;/span&gt; root &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--scm-host&lt;/span&gt; &amp;lt;scm_host&amp;gt; scm scm scm
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;需要注意以下事项：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;运行该命令，需要确保能够以 root 身份远程登录 MySQL 服务器&lt;/li&gt;
  &lt;li&gt;该命令会修改 &lt;code class=&quot;highlighter-rouge&quot;&gt;/etc/cloudera-scm-server/db.properties&lt;/code&gt; 文件中的相关配置&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;mysql_host&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;scm_host&lt;/code&gt; 最好使用域名&lt;/li&gt;
  &lt;li&gt;要确保 &lt;code class=&quot;highlighter-rouge&quot;&gt;/usr/share/cmf/lib&lt;/code&gt; 目录下包含 MySQL 数据库的驱动&lt;/li&gt;
  &lt;li&gt;也可以手动创建数据库，并修改 &lt;code class=&quot;highlighter-rouge&quot;&gt;db.properties&lt;/code&gt; 文件&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;43准备-hive-数据库&quot;&gt;4.3、准备 Hive 数据库&lt;/h2&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mysql -u root -p
mysql&amp;gt; CREATE DATABASE hive DEFAULT CHARACTER SET UTF8;
mysql&amp;gt; GRANT ALL PRIVILEGES ON hive.* TO 'hive'@'%' IDENTIFIED BY 'hive';
mysql&amp;gt; FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;44准备-oozie-数据库&quot;&gt;4.4、准备 Oozie 数据库&lt;/h2&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mysql -u root -p
mysql&amp;gt; CREATE DATABASE oozie DEFAULT CHARACTER SET UTF8;
mysql&amp;gt; GRANT ALL PRIVILEGES ON oozie.* TO 'oozie'@'%' IDENTIFIED BY 'oozie';
mysql&amp;gt; FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;45准备-hue-数据库&quot;&gt;4.5、准备 Hue 数据库&lt;/h2&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ mysql -u root -p
mysql&amp;gt; CREATE DATABASE hue DEFAULT CHARACTER SET UTF8;
mysql&amp;gt; GRANT ALL PRIVILEGES ON hue.* TO 'hue'@'%' IDENTIFIED BY 'hue';
mysql&amp;gt; FLUSH PRIVILEGES;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;5启动-cloudera-manager-服务&quot;&gt;5、启动 Cloudera Manager 服务&lt;/h1&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;service cloudera-scm-server start
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;6cloudera-manager-安装过程&quot;&gt;6、Cloudera Manager 安装过程&lt;/h1&gt;

&lt;p&gt;(1) 使用默认用户名和密码 &lt;code class=&quot;highlighter-rouge&quot;&gt;admin/admin&lt;/code&gt; 登录&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/1.png&quot; alt=&quot;使用默认用户名和密码 admin/admin 登录&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(2) 接受 License&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/2.png&quot; alt=&quot;接受 License&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(3) 选择安装类别&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/3.png&quot; alt=&quot;选择安装类别&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(4) 即将安装的组件列表&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/4.png&quot; alt=&quot;即将安装的组件列表&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(5) 在输入框内，填写需要管理的主机地址&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/5.png&quot; alt=&quot;在输入框内，填写需要管理的主机地址&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(6) 确认主机&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/6.png&quot; alt=&quot;确认主机&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(7) 使用 Parcels 进行安装&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/7.png&quot; alt=&quot;使用 Parcels 进行安装&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/8.png&quot; alt=&quot;使用 Parcels 进行安装&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(8) 配置 Parcels 的安装目录，如果提示目录不存在，需手动创建，并确保目录的 Owner 为 &lt;code class=&quot;highlighter-rouge&quot;&gt;cloudera-scm:cloudera-scm&lt;/code&gt; 用户&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/9.png&quot; alt=&quot;配置 Parcels 的安装目录&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(9) 选择是否需要安装 Oracle JDK。因为我们已经手动安装过了，因此这一步可以跳过，不用勾选&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/10.png&quot; alt=&quot;选择是否需要安装 Oracle JDK&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(10) 是否使用单用户模式安装，不用勾选&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/11.png&quot; alt=&quot;是否使用单用户模式安装，不用勾选&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(11) 指定登录主机的用户名和密码，也可以使用密钥登录&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/12.png&quot; alt=&quot;指定登录主机的用户名和密码，也可以使用密钥登录&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(12) 准备服务器。因为我们此前已经手动安装好了 cloudera-manager-daemons 和 cloudera-manager-agent 这一步会比较快。如果前面没有手动安装，那么这里会自动安装，就要等待比较久的时间&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/13.png&quot; alt=&quot;准备服务器&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(13) 服务器准备完成&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/14.png&quot; alt=&quot;服务器准备完成&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(14) 下载、分发、解压和激活 Parcels&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/15.png&quot; alt=&quot;下载、分发、解压和激活 Parcels&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(15) 完成&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/16.png&quot; alt=&quot;完成&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(16) 可以不用继续下面的操作，直接返回就能够看到管理界面了，其他的服务可以后续手动安装&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/17.png&quot; alt=&quot;接返回 Cloudera Manager&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;7安装其他组件&quot;&gt;7、安装其他组件&lt;/h1&gt;

&lt;h2 id=&quot;71安装-zookeeper&quot;&gt;7.1、安装 Zookeeper&lt;/h2&gt;

&lt;p&gt;(1) 选择 Add Service 菜单&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/18.png&quot; alt=&quot;选择 Add Service 菜单&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(2) 选择需要安装的服务&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/19.png&quot; alt=&quot;选择需要安装的服务&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(3) 选择需要部署此服务的主机&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/20.png&quot; alt=&quot;选择需要部署此服务的主机&quot; /&gt;&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/21.png&quot; alt=&quot;对一些核心参数进行配置。&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(4) 对一些核心参数进行配置。因为所用的虚拟机默认 &lt;code class=&quot;highlighter-rouge&quot;&gt;/var&lt;/code&gt; 目录容量比较小，因此需要做一些调整。如果参数设置不当，Cloudera Manager 会有告警，可以在安装完成以后根据提示进行修改&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/22.png&quot; alt=&quot;等待服务启动&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(5) 等待服务启动&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/23.png&quot; alt=&quot;服务启动完成&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(6) 服务启动完成&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/24.png&quot; alt=&quot;服务启动完成&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(7) 安装完成&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/25.png&quot; alt=&quot;安装完成&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;72安装-cloudera-management-service-服务&quot;&gt;7.2、安装 Cloudera Management Service 服务&lt;/h2&gt;

&lt;p&gt;(1) 点击右上角的 Add Cloudera Management Service 按钮&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/26.png&quot; alt=&quot;点击右上角的 Add Cloudera Management Service 按钮&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(2) 配置参数&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/27.png&quot; alt=&quot;配置参数&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(3) 安装完成，启动服务&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/28.png&quot; alt=&quot;安装完成，启动服务&quot; /&gt;&lt;/p&gt;

&lt;p&gt;(4) 等待服务启动完成&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/cdh/29.png&quot; alt=&quot;等待服务启动完成&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;73安装其它服务&quot;&gt;7.3、安装其它服务&lt;/h2&gt;

&lt;p&gt;其它服务的安装方式大同小异，在这里就不再截图叙述，按照提示一步步完成即可。如果参数配置有误，系统会有告警，根据告警提示进行相应的修改即可。实在不行也可以直接删除，从头再来。&lt;/p&gt;

&lt;h1 id=&quot;8常见问题&quot;&gt;8、常见问题&lt;/h1&gt;

&lt;h2 id=&quot;81安装-hive-时找不到-jdbc-driver&quot;&gt;8.1、安装 Hive 时找不到 JDBC Driver&lt;/h2&gt;

&lt;p&gt;请参考：&lt;a href=&quot;http://fatkun.com/2015/05/cloudera-manage-jdbc-driver-cannot-be-found.html&quot;&gt;Cloudera Manager 添加 Hive 时报错找不到 JDBC Driver&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;将 MySQL Connector Java 复制到 &lt;code class=&quot;highlighter-rouge&quot;&gt;/usr/share/java&lt;/code&gt; 目录下，但是文件名要命名为 &lt;code class=&quot;highlighter-rouge&quot;&gt;mysql-connector-java.jar&lt;/code&gt;。&lt;/p&gt;

&lt;h2 id=&quot;82设置-swappiness&quot;&gt;8.2、设置 swappiness&lt;/h2&gt;

&lt;p&gt;请参考：&lt;a href=&quot;https://www.hostingstuff.net/tweak-swap-centos-7/&quot;&gt;Tweak Swap on CentOS 7&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;su -
sysctl vm.swappiness&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;10
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;vm.swappiness = 10&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/sysctl.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;83修改内核参数&quot;&gt;8.3、修改内核参数&lt;/h2&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;su -
&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;never &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /sys/kernel/mm/transparent_hugepage/defrag 
&lt;span class=&quot;nb&quot;&gt;echo &lt;/span&gt;never &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /sys/kernel/mm/transparent_hugepage/enabled 

&lt;span class=&quot;c&quot;&gt;# 并将如下两个语句写入 /etc/rc.local 文件中&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;kudu soft nofile 5242880&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/security/limits.conf
&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;kudu hard nofile 5242880&quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;gt;&amp;gt;&lt;/span&gt; /etc/security/limits.conf
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
</description>
        <pubDate>Thu, 03 Aug 2017 02:06:00 +0000</pubDate>
        <link>http://www.tangrui.net/2017/deploy-cloudera-manager-on-centos-6.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2017/deploy-cloudera-manager-on-centos-6.html</guid>
        
        <category>大数据</category>
        
        <category>CDH</category>
        
        <category>Cloudera</category>
        
        <category>Hadoop</category>
        
        
        <category>技术</category>
        
      </item>
    
      <item>
        <title>精英人才之路</title>
        <description>&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;没有 KPI 的团队不可能成为虎狼之师，没有 deadline 的产品不可能顺利收官，没有 I want you 的比赛也不可能选拔出优秀的选手，凡此种种，说的都是一个选择。无论你是依从内心，完成自我期许的价值；还是顺应客户，实现他人期待的目标，唯有那些超脱了选择的标杆，完成龙门一跃之被选择者，才有可能蜕变为真正的精英。&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;p&gt;自我突破的结果也有可能是自我毁灭，因为这是一条用自己的短板搏别人长版的如履薄冰之路，所以能够真正走完这条路的人也寥寥无几。回到我们的第一个话题，没有人愿意主动选择这么一条凶险的路途，而唯有环境的迫使。正如在其位、谋其政，屁股决定脑袋。&lt;/p&gt;

&lt;p&gt;创业团队中最有可能涌现自我突破之人，因为在资源有限的情况下，每个人都不得不尝试一些自己并不擅长的工作。这也不难理解，为什么创业成功的团队少之又少了——每个个体独立的自我突破才有可能汇集成整个团队的蜕变。&lt;/p&gt;

&lt;p&gt;自我突破有两个前提：首先需要承认自己的无知，其次就是要善于学习。&lt;/p&gt;

&lt;p&gt;人类的历史充满了愚昧，面对死亡，人类不是求助于医学，而是拜倒在宗教的权威之下。宗教认为自身无所不知，也不鼓励质疑，就算那些宗教无法解释的问题，通常也会被轻描淡写为：这些都无从轻重，以至于神灵并没有告诉人类。科技在近 100 年间突飞猛进，正是因为我们越发承认自己的无知。当一个实验或观测结果与现有理论有悖的时候，我们更倾向于怀疑而并非固执己见，这样的思维转变催生了一代又一代的精英伟人。&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;
</description>
        <pubDate>Mon, 17 Jul 2017 14:11:00 +0000</pubDate>
        <link>http://www.tangrui.net/2017/road-to-elite.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2017/road-to-elite.html</guid>
        
        <category>演讲</category>
        
        
        <category>其他</category>
        
      </item>
    
      <item>
        <title>Pinpoint 插件开发</title>
        <description>&lt;h1 id=&quot;一运行-pinpoint-系统&quot;&gt;一、运行 Pinpoint 系统&lt;/h1&gt;

&lt;p&gt;运行 Pinpoint 系统最简单的办法是使用 Docker。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 运行 Pinpoint&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git clone https://github.com/dawidmalina/docker-pinpoint
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;docker-pinpoint
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose up &lt;span class=&quot;nt&quot;&gt;-d&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h1 id=&quot;二编译环境搭建&quot;&gt;二、编译环境搭建&lt;/h1&gt;

&lt;p&gt;编译 Pinpoint 1.5.2 的源代码需要 JDK 6、JDK 7+ 以及 Maven 3.2.x+ 的支持，符合以上要求的最新版本的编译工具列表如下：&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;th&gt;备注&lt;/th&gt;
    &lt;/tr&gt;
  &lt;/thead&gt;
  &lt;tbody&gt;
    &lt;tr&gt;
      &lt;td&gt;JDK 6&lt;/td&gt;
      &lt;td&gt;JDK 6u45&lt;/td&gt;
      &lt;td&gt;已经停止更新&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;JDK 7+&lt;/td&gt;
      &lt;td&gt;JDK 8u112&lt;/td&gt;
      &lt;td&gt; &lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Maven 3.2.x+&lt;/td&gt;
      &lt;td&gt;Maven 3.2.9&lt;/td&gt;
      &lt;td&gt;之所以使用 Maven 3.2.x，猜想是因为只有 Maven 3.2.x 才支持 JDK 6&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;并且还要设置两个环境变量：&lt;em&gt;JAVA_6_HOME&lt;/em&gt; 和 &lt;em&gt;JAVA_7_HOME&lt;/em&gt; 分别指向对应的 JDK 安装目录，然后运行以下命令完成编译：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 编译 Pinpoint&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;git clone https://github.com/naver/pinpoint
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;pinpoint
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mvn install &lt;span class=&quot;nt&quot;&gt;-Dmaven&lt;/span&gt;.test.skip&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用 Docker 来运行编译会更加容易，这免去了环境配置的需要。首先将 Pinpoint 的源代码下载到本地目录，例如 &lt;code class=&quot;highlighter-rouge&quot;&gt;/projects/pinpoint&lt;/code&gt;，然后运行命令：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 使用 Docker 编译 Pinpoint&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker run &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; /projects/pinpoint:/pinpoint:rw &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &amp;lt;/path/to/.m2&amp;gt;:/root/.m2:rw tangrui/pinpoint-development
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中的两个 &lt;strong&gt;-v&lt;/strong&gt; 参数是用来映射目录的。第一个 &lt;strong&gt;-v&lt;/strong&gt; 参数是将本地的 &lt;code class=&quot;highlighter-rouge&quot;&gt;/projects/pinpoint&lt;/code&gt; 目录映射到容器的 &lt;code class=&quot;highlighter-rouge&quot;&gt;/pinpoint&lt;/code&gt; 目录；而第二个 &lt;strong&gt;-v&lt;/strong&gt; 参数是将本地的 Maven 存储库映射到容器的 &lt;code class=&quot;highlighter-rouge&quot;&gt;/root/.m2&lt;/code&gt; 目录，这样做的目的是让本地存储库与 Docker 中的存储库共享内容，避免每次编译的时候都要从网络上下载大量依赖包，提升运行效率。&lt;/p&gt;

&lt;h1 id=&quot;三技术概述&quot;&gt;三、技术概述&lt;/h1&gt;

&lt;h2 id=&quot;31架构组成&quot;&gt;3.1、架构组成&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/pinpoint-architecture.png&quot; alt=&quot;Pinpoint 架构&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pinpoint 主要由 3 个组件外加 Hbase 数据库构成，三个组件分别为：Agent、Collector 和 Web UI。&lt;/p&gt;

&lt;h2 id=&quot;32系统特色&quot;&gt;3.2、系统特色&lt;/h2&gt;

&lt;ol&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;/ol&gt;

&lt;h2 id=&quot;33分布式追踪系统如何工作&quot;&gt;3.3、分布式追踪系统如何工作&lt;/h2&gt;

&lt;p&gt;无论 Pinpoint 还是 Zipkin，都是基于 Google Dapper 的论文实现的。其核心思想就是在服务各节点彼此调用的时候，记录并传递一个应用级别的标记，这个标记可以用来关联各个服务节点之间的关系。比如两个节点之间使用 HTTP 作为请求协议的话，那么这些标记就会被加入到 HTTP 头中。因此如何传递这些标记是与节点之间使用的通讯协议有关的，有些协议就很容易加入这样的内容，但有些协议就相对困难甚至不可能，因此这一点就直接决定了实现分布式追踪系统的难度。&lt;/p&gt;

&lt;h2 id=&quot;34pinpoint-的数据结构&quot;&gt;3.4、Pinpoint 的数据结构&lt;/h2&gt;

&lt;p&gt;Pinpoint 消息的数据结构主要包含三种类型 Span，Trace 和 TraceId。&lt;/p&gt;

&lt;p&gt;Span 是最基本的调用追踪单元，当远程调用到达的时候，Span 指代处理该调用的作业，并且携带追踪数据。为了实现代码级别的可见性，Span 下面还包含一层 SpanEvent 的数据结构。每个 Span 都包含一个 SpanId。&lt;/p&gt;

&lt;p&gt;Trace 是一组相互关联的 Span 集合，同一个 Trace 下的 Span 共享一个 TransactionId，而且会按照 SpanId 和 ParentSpanId 排列成一棵有层级关系的树形结构。&lt;/p&gt;

&lt;p&gt;TraceId 是 TransactionId、SpanId 和 ParentSpanId 的组合。TransactionId (TxId) 是一个交易下的横跨整个分布式系统收发消息的 ID，其必须在整个服务器组中是全局唯一的。也就是说 TransactionId 识别了整个调用链；SpanId (SpanId) 是处理远程调用作业的 ID，当一个调用到达一个节点的时候随即产生；ParentSpanId (pSpanId) 顾名思义，就是产生当前 Span 的调用方 Span 的 ID。如果一个节点是交易的最初发起方，其 ParentSpanId 是 -1，以标志其是整个交易的根 Span。下图能够比较直观的说明这些 ID 结构之间的关系。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/pinpoint-ids.png&quot; alt=&quot;Pinpoint ID 结构之间的关系&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;35如何使用-java-agent&quot;&gt;3.5、如何使用 Java Agent&lt;/h2&gt;

&lt;p&gt;Pinpoint 的优势在于其使用 Java Agent 向节点应用注入字节码，而不是直接修改源代码。因此部署一个节点就变得非常容易，只需要在程序启动的时候加入如下一些启动参数：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# 使用 Java Agent
-javaagent:$AGENT_PATH/pinpoint-bootstrap-$VERSION.jar
-Dpinpoint.agentId=&amp;lt;Agent's UniqueId&amp;gt;
-Dpinpoint.applicationName=&amp;lt;The name indicating a same service (AgentId collection)&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;36代码注入是如何工作的&quot;&gt;3.6、代码注入是如何工作的&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/pinpoint-bytecode-instrument.png&quot; alt=&quot;Pinpoint 字节码注入&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Pinpoint 对代码注入的封装非常类似 AOP，当一个类被加载的时候会通过 Interceptor 向指定的方法前后注入 before 和 after 逻辑，在这些逻辑中可以获取系统运行的状态，并通过 TraceContext 创建 Trace 消息，并发送给 Pinpoint 服务器。但与 AOP 不同的是，Pinpoint 在封装接口的时候考虑到了更多与目标代码的交互能力，因此用 Pinpoint 提供的 API 来编写注入逻辑会比 AOP 看起来更加容易和专业。(这些内容后面会有更详细说明)&lt;/p&gt;

&lt;h2 id=&quot;37pinpoint-的应用实例&quot;&gt;3.7、Pinpoint 的应用实例&lt;/h2&gt;

&lt;p&gt;下图展现了两个 Tomcat 服务器应用了 Pinpoint 之后，所收集到的追踪数据。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/pinpoint-data-collection.png&quot; alt=&quot;Pinpoint 数据收集&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;四agent-插件开发&quot;&gt;四、Agent 插件开发&lt;/h1&gt;

&lt;p&gt;开发 Pinpoint Agent 插件只需要关注两个接口：TraceMetadataProvider 和 ProfilerPlugin，实现类通过 Java 的服务发现机制进行加载。&lt;/p&gt;

&lt;h2 id=&quot;41serviceloader-配置&quot;&gt;4.1、ServiceLoader 配置&lt;/h2&gt;

&lt;p&gt;Pinpoint 的插件是以 jar 包的形式部署的，为了使得 Pinpoint Agent 能够定位到 TraceMetadataProvider 和 ProfilerPlugin 两个接口的实现，需要在 &lt;code class=&quot;highlighter-rouge&quot;&gt;META-INF/services&lt;/code&gt; 目录下创建两个文件：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;META-INF/services/com.navercorp.pinpoint.common.trace.TraceMetadataProvider
META-INF/services/com.navercorp.pinpoint.bootstrap.plugin.ProfilerPlugin
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这两个文件中的每一行都写明对应实现类的全名称即可。&lt;/p&gt;

&lt;h2 id=&quot;42tracemetadataprovider&quot;&gt;4.2、TraceMetadataProvider&lt;/h2&gt;

&lt;p&gt;TraceMetadataProvider 提供了对 ServiceType 和 AnnotationKey 的管理。&lt;/p&gt;

&lt;h2 id=&quot;421servicetype&quot;&gt;4.2.1、ServiceType&lt;/h2&gt;

&lt;p&gt;每个 Span 和 SpanEvent 都包含一个 ServiceType，用来标明他们属于哪一个库 (例如 Jetty、MySQL JDBC Client 或者 Apache HTTP Client 等)，以及追踪此类型服务的 Span 和 SpanEvent 该如何被处理。ServiceType 的数据结构如下：&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;name&lt;/td&gt;
      &lt;td&gt;ServiceType 的名称，必须唯一&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;code&lt;/td&gt;
      &lt;td&gt;ServiceType 的编码，短整形，必须唯一&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;desc&lt;/td&gt;
      &lt;td&gt;描述&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;properties&lt;/td&gt;
      &lt;td&gt;附加属性&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;Pinpoint 为了尽量压缩 Agent 到 Collector 数据包的大小，ServiceType 被设计成不是以序列化字符串的形式发送的，而是以整形数字发送的 (code 字段)，这就需要建立一个映射关系，将 code 转换成对应的 ServiceType 实例，这个映射机制就是由 TraceMetadataProvider 负责的。&lt;/p&gt;

&lt;p&gt;ServiceType 的 code 必须全局唯一，为了避免冲突，Pinpoint 官方对这个映射表进行了严格的管理，如果所开发的插件想要声明新的映射关系，需要通知 Pinpoint 团队，以便对此映射表进行更新和发布。与私有 IP 地址段一样，Pinpoint 团队也保留了一段私有区域可供开发内部服务的时候使用。具体的 ID 范围参照表如下：&lt;/p&gt;

&lt;p&gt;ServiceType Code 全部范围&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;Internal Use&lt;/td&gt;
      &lt;td&gt;0 ~ 999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Server&lt;/td&gt;
      &lt;td&gt;1000 ~ 1999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DB Client&lt;/td&gt;
      &lt;td&gt;2000 ~ 2999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cache Client&lt;/td&gt;
      &lt;td&gt;8000 ~ 8999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RPC Client&lt;/td&gt;
      &lt;td&gt;9000 ~ 9999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Others&lt;/td&gt;
      &lt;td&gt;5000 ~ 7999&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;ServiceType Code 私有区域范围&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;Server&lt;/td&gt;
      &lt;td&gt;1900 ~ 1999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;DB Client&lt;/td&gt;
      &lt;td&gt;2900 ~ 2999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Cache Client&lt;/td&gt;
      &lt;td&gt;8900 ~ 8999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;RPC Client&lt;/td&gt;
      &lt;td&gt;9900 ~ 9999&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;Others&lt;/td&gt;
      &lt;td&gt;7500 ~ 7999&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;h2 id=&quot;422annotationkey&quot;&gt;4.2.2、AnnotationKey&lt;/h2&gt;

&lt;p&gt;Annotation 是包含在 Span 和 SpanEvent 中的更详尽的数据，以键值对的形式存在，键就是 AnnotationKey，值可以是字符串或字节数组。Pinpoint 内置了很多的 AnnotationKey，如果不够用的话也可以通过 TraceMetadataProvider 来自定义。AnnotationKey 的数据结构如下：&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;name&lt;/td&gt;
      &lt;td&gt;AnnotationKey 的名称&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;code&lt;/td&gt;
      &lt;td&gt;AnnotationKey 的编码，整形，必须唯一&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;properties&lt;/td&gt;
      &lt;td&gt;附加属性&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;同 ServiceType 的 code 字段一样，AnnotationKey 的 code 字段也是全局唯一的，Pinpoint 团队给出的私有区域范围是 900 到 999。&lt;/p&gt;

&lt;h3 id=&quot;423tracemetadataprovider-接口&quot;&gt;4.2.3、TraceMetadataProvider 接口&lt;/h3&gt;

&lt;p&gt;TraceMetadataProvider 接口只有一个 setup 方法，此方法接收一个 TraceMetadataSetupContext 类型的参数，该类型有三个方法：&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;addServiceType(ServiceType)&lt;/td&gt;
      &lt;td&gt;注册 ServiceType&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;addServiceType(ServiceType, AnnotationKeyMatcher)&lt;/td&gt;
      &lt;td&gt;注册 ServiceType，并将匹配 AnnotationKeyMatcher 的 AnnotationKey 作为此 ServiceType 的典型注解，这些典型注解会显示在瀑布视图的 Argument 列中&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
      &lt;td&gt;addAnnotationKey(AnnotationKey)&lt;/td&gt;
      &lt;td&gt;注册 AnnotationKey，这里注册的 AnnotationKey 会被标记为 VIEW_IN_RECORD_SET，显示在瀑布视图中是以单独一行显示的，且前面有一个蓝色的 i 图标&lt;/td&gt;
    &lt;/tr&gt;
  &lt;/tbody&gt;
&lt;/table&gt;

&lt;p&gt;详细使用方法可以参考官方提供的样例文件 &lt;a href=&quot;https://github.com/naver/pinpoint-plugin-sample/blob/master/plugin/src/main/java/com/navercorp/pinpoint/plugin/sample/SampleTraceMetadataProvider.java&quot;&gt;SampleTraceMetadataProvider&lt;/a&gt;。&lt;/p&gt;

&lt;h3 id=&quot;43profilerplugin&quot;&gt;4.3、ProfilerPlugin&lt;/h3&gt;

&lt;p&gt;ProfilerPlugin 通过字节码注入的方式拦截目标代码以实现跟踪数据的收集。&lt;/p&gt;

&lt;h3 id=&quot;431插件的工作原理&quot;&gt;4.3.1、插件的工作原理&lt;/h3&gt;

&lt;ol&gt;
  &lt;li&gt;Pinpoint Agent 随 JVM 一起启动&lt;/li&gt;
  &lt;li&gt;Agent 加载所有 &lt;code class=&quot;highlighter-rouge&quot;&gt;plugin&lt;/code&gt; 目录下的插件&lt;/li&gt;
  &lt;li&gt;Agent 调用每个已经加载的插件的 ProfilerPlugin.setup(ProfilerPluginSetupContext) 方法&lt;/li&gt;
  &lt;li&gt;在 setup 方法中，插件定义那些需要被转换的类，并注册 TransformerCallback&lt;/li&gt;
  &lt;li&gt;目标应用启动&lt;/li&gt;
  &lt;li&gt;当类被加载的时候，Pinpoint Agent 会寻找注册到该类的 TransformerCallback&lt;/li&gt;
  &lt;li&gt;如果 TransformerCallback 被注册，Agent 就调用它的 doInTransform 方法&lt;/li&gt;
  &lt;li&gt;TransformerCallback 修改目标类的字节码 (例如添加拦截器、添加字段等)&lt;/li&gt;
  &lt;li&gt;修改后的代码返回到 JVM，类型加载的时候就使用修改后的字节码&lt;/li&gt;
  &lt;li&gt;应用程序继续&lt;/li&gt;
  &lt;li&gt;当调用到被修改的方法的时候，拦截器的 before 和 after 方法被调用&lt;/li&gt;
  &lt;li&gt;拦截器记录追踪数据&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pinpoint 插件的工作原理看似跟 AOP 非常相似，但还是有一些区别和自身特色的：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;因为 Pinpoint 需要处理的注入场景比较单一，因此他提供的注入 API 相对简单；而 AOP 为了要处理各种可能的切面情况，Pointcut 被设计得非常复杂。&lt;/li&gt;
  &lt;li&gt;Pinpoint 插件拦截是通过拦截器的 before 和 after 方法实现的，很像 around 切面，如果不想执行其中某个方法，可以通过 @IgnoreMethod 注解来忽略。&lt;/li&gt;
  &lt;li&gt;Pinpoint 的拦截器可以任意拦截方法，因此被拦截的方法之间可能会有调用关系，这会导致追踪数据被重复收集，因此 Pinpoint 提供了 Scope 和 ExecutionPolicy 功能。在一个 Scope 内，可以定义拦截器的执行策略：是每次都执行 (ExecutionPolicy.ALWAYS)，还是在没有更外层的拦截器存在的时候执行 (ExecutionPolicy.BOUNDARY)，或者必须在有外层拦截器存在的时候执行 (ExecutionPolicy.INTERNAL)。具体请参考这个&lt;a href=&quot;https://github.com/naver/pinpoint-plugin-sample/tree/master/plugin/src/main/java/com/navercorp/pinpoint/plugin/sample/_03_Interceptor_Scope__Prevent_Duplicated_Trace&quot;&gt;样例&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;在一个 Scope 内的拦截器彼此还可以传递数据。同一个 Scope 内的拦截器共享一个 InterceptorScopeInvocation 对象，可以使用该对象来交换数据，请参考&lt;a href=&quot;https://github.com/naver/pinpoint-plugin-sample/tree/master/plugin/src/main/java/com/navercorp/pinpoint/plugin/sample/_04_Interceptor_Scope__Data_Sharing&quot;&gt;样例&lt;/a&gt;。&lt;/li&gt;
  &lt;li&gt;除了拦截方法以外，Pinpoint 还可以向目标类中注入字段以及 getter 和 setter 方法，可以使用它们来保存一些上下文的数据。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;通过上述内容可以了解，如果要编写一个 Pinpoint 的插件，除了要对目标代码的调用逻辑有较深入的理解，还必须得设计好上下文数据如何存储、如何传递，以及如何通过 Scope 避免信息被重复收集等问题。这些问题在 AOP 的场景下也会存在，只是 Pinpoint 提供了更加一致和便捷的解决方案，而基于 AOP 的实现就要自己去考虑这些问题了。&lt;/p&gt;

&lt;h3 id=&quot;432方法拦截&quot;&gt;4.3.2、方法拦截&lt;/h3&gt;

&lt;p&gt;如前文所述，Pinpoint 插件需要实现 ProfilerPlugin 接口，该接口只有一个 setup(ProfilerPluginSetupContext) 方法。为了更容易的操作 Pinpoint 的代码注入 API，还需要实现一个 TransformTemplateAware 的接口，该接口会注入 TransformTemplate 类。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SamplePlugin&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ProfilerPlugin&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TransformTemplateAware&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;TransformTemplate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transformTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setup&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;ProfilerPluginSetupContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setTransformTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TransformTemplate&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transformTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;transformTemplate&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;transformTemplate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;ProfilerPluginSetupContext 有两个方法：getConfig() 和 addApplicationTypeDetector(ApplicationTypeDetector…)。第一个方法用来获取 ProfilerConfig 对象，该对象保存了所有插件的配置信息，而第二个方法用来添加 ApplicationTypeDetector。ApplicationTypeDetector 是用来自动检测节点所运行服务的类型的。例如在 pinpoint-tomcat-plugin 项目中，有 TomcatDetector 类，这个类的作用是通过如下逻辑检测来确定当前服务是否为 Tomcat 的：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;检查 main class 是不是 org.apache.catalina.startup.Bootstrap&lt;/li&gt;
  &lt;li&gt;检查是否有系统变量 &lt;em&gt;catalina.home&lt;/em&gt;&lt;/li&gt;
  &lt;li&gt;检查是否存在某个指定的类 (对于检测 Tomcat 而言，这个特定的类型也是 org.apache.catalina.startup.Bootstrap)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;如果以上三个条件都满足，就把当前节点的 ServiceType 设置为 Tomcat。&lt;/p&gt;

&lt;p&gt;TransformTemplate 只有一个方法 transform(String, TransformCallback)，第一个参数是需要被转换的类的全名称，而第二个参数就是 4.3.1 章节中提到的 TransformCallback 接口，这个接口也只有一个方法叫 doInTransform，所有的注入逻辑都在这里完成。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;doInTransform&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Instrumentor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrumentor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ClassLoader&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;classLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;?&amp;gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;classBeingRedefined&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;ProtectionDomain&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;protectionDomain&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;byte&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;classfileBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InstrumentException&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 1. Get InstrumentClass of the target class&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;InstrumentClass&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instrumentor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getInstrumentClass&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;classLoader&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;className&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;classfileBuffer&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 2. Get InstrumentMethod of the target method.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;InstrumentMethod&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;targetMethod&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getDeclaredMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;targetMethod&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;java.lang.String&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 3. Add interceptor. The first argument is FQN of the interceptor class,&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;// followed by arguments for the interceptor's constructor.&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;targetMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;addInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.navercorp.pinpoint.bootstrap.interceptor.BasicMethodInterceptor&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;va&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;SamplePluginConstants&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;MY_SERVICE_TYPE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 4. Return resulting byte code.&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toBytecode&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ol&gt;
  &lt;li&gt;注入过程是从获取 InstrumentClass 类开始的。&lt;/li&gt;
  &lt;li&gt;如果想要拦截一个方法，或者是添加字段以及 getter、setter 方法，就可以调用 InstrumentClass 对应的 API 来实现，这里是获取了一个签名为 targetMethod(String) 的方法，返回的对象是 InstrumentMethod 类型。&lt;/li&gt;
  &lt;li&gt;调用 InstrumentMethod 的 addInterceptor 方法注入拦截器，所有跟踪信息的收集行为都是在拦截器中实现的，这里添加的拦截器是 com.navercorp.pinpoint.bootstrap.interceptor.BasicMethodInterceptor，这是 Pinpoint 框架默认提供的一个基本拦截器，里面收集了一些 targetMethod 的调用信息。后面的 va 是一个静态方法 (代表可变参数列表)，va 中给出的参数会传递到 BasicMethodInterceptor 的构造方法中。&lt;/li&gt;
  &lt;li&gt;调用 InstrumentClass.toBytecode() 方法即可返回注入后的字节码，剩下的事情就转交给 Pinpoint Agent 自己来完成了。&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;BasicMethodInterceptor 类仅提供了对方法调用信息的简单收集，只收集方法的名称、参数、返回值以及是否产生异常等等。在某些复杂的场景下，我们会需要收集更多的信息，如当前登录用户、线程池、数据库查询语句以及任何跟中间件功能有关的信息，这就需要我们定义自己的 Interceptor 类。&lt;/p&gt;

&lt;p&gt;以上内容请参考该&lt;a href=&quot;https://github.com/naver/pinpoint-plugin-sample/tree/master/plugin/src/main/java/com/navercorp/pinpoint/plugin/sample/_01_Injecting_BasicMethodInterceptor&quot;&gt;样例&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Interceptor 是一个标记接口，真正有意义的是 AroundInterceptor 接口，该接口定义了如下两个方法：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;AroundInterceptor&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;extends&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Interceptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;before&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;after&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Object&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Throwable&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;throwable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;为了应对被拦截方法的不同个数的参数列表，AroundInterceptor 还有若干子接口：AroundInterceptor0, AroundInterceptor1，…，AroundInterceptor5，分别对应没有参数，一个参数，到 5 个参数的方法。实现 Interceptor 接口的时候要提供一个如下的构造方法：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;RecordArgsAndReturnValueInterceptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;TraceContext&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;traceContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;MethodDescriptor&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;traceContext&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;traceContext&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;descriptor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;descriptor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;TraceContext 和 MethodDescriptor 会被 Pinpoint Agent 在运行时注入进来，当然也可以添加额外的参数，这些额外的参数，需要在 addInterceptor 的时候指定，就像上文中关于 va 的描述那样。&lt;/p&gt;

&lt;p&gt;有了 TraceContext 对象，就可以开始收集信息了。调用 traceContext.getCurrentTraceObject() 方法可以获取当前的 Trace，再调用 trace.traceBlockBegin() 开始记录一个新的 Trace 块 (这里我理解应该就是 Span 了)。在 traceBlockBegin 以后，可以调用 currentSpanEventRecorder 方法获取 SpanEventRecorder 对象，这个对象提供了诸如 recordServiceType、recordApi、recordException 和 recordAttribute 等方法，可以分别记录方法的有关信息。但是 SpanEventRecorder 并没有提供 recordReturnValue 这样的方法，只能通过 recordAttribute 来记录。所有自己扩展的信息也是通过 recordAttribute 来记录的。最后所有信息记录完成就调用 traceBlockEnd() 方法关闭区块。&lt;/p&gt;

&lt;p&gt;以上内容请参考该&lt;a href=&quot;https://github.com/naver/pinpoint-plugin-sample/tree/master/plugin/src/main/java/com/navercorp/pinpoint/plugin/sample/_02_Injecting_Custom_Interceptor&quot;&gt;样例&lt;/a&gt;。&lt;/p&gt;

&lt;h1 id=&quot;五总结&quot;&gt;五、总结&lt;/h1&gt;

&lt;p&gt;其实 Pinpint 的插件开发 API 还提供了非常丰富的能力，如拦截异步方法、调用链跟踪、拦截器之间共享数据等等，但原理都是基于上述这些内容，只是调用了更加复杂的 API 而已。具体代码可以参考官方提供的&lt;a href=&quot;https://github.com/naver/pinpoint-plugin-sample&quot;&gt;样例项目&lt;/a&gt;，里面有非常详尽的代码及注释，相信理解了上面这些内容，再看这个代码就不会有任何困难了。&lt;/p&gt;
</description>
        <pubDate>Tue, 22 Nov 2016 14:45:03 +0000</pubDate>
        <link>http://www.tangrui.net/2016/pinpoint-plugin-development.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2016/pinpoint-plugin-development.html</guid>
        
        <category>架构</category>
        
        <category>Pinpoint</category>
        
        <category>Dapper</category>
        
        <category>APM</category>
        
        <category>微服务</category>
        
        <category>服务监控</category>
        
        
        <category>技术</category>
        
      </item>
    
      <item>
        <title>使用 Dubbo 对遗留单体系统进行微服务改造</title>
        <description>&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/stone-henge.jpg&quot; alt=&quot;巨石阵&quot; /&gt;&lt;br /&gt;
&lt;small&gt;Credit: &lt;a href=&quot;https://www.flickr.com/photos/110569055@N04/&quot;&gt;Justin Kenneth Rowley&lt;/a&gt;. You can find the original photo at &lt;a href=&quot;https://www.flickr.com/photos/110569055@N04/25341920334/in/photolist-EBnY1W-aguFHZ-D2DhnV-AC7Wp6-zTUqUV-hwq3HA-CJwnX7-CL4umw-VgEL9g-nuAybE-atpa2p-preFYp-V9rtZj-WaTqzf-ChJtWh-pvpTX1-8qsZbi-aD13TS-VNGiy7-V6v5fz-2PDDnT-RRiHZz-pcEG7c-XmYL5X-npMaVu-vHw5Q-SarYac-SarYti-8vsQ5G-pv9Phr-jYZUzi-az5qfo-Vakgmt-AKVTJm-E1sqMN-5FKt8R-oR4jnP-dmQ6Fu-oSSnLG-W8ZTHJ-f1ZRNS-qiTgmt-oazXai-QMScS7-cNrmy3-5FPJjS-fp84Ev-oBGJGq-4UR3cs-drvG8d&quot;&gt;flickr&lt;/a&gt;.&lt;/small&gt;&lt;/p&gt;

&lt;blockquote&gt;
  &lt;p&gt;The microservices style of architecture highlights rising abstractions in the developer world because of containerization and the emphasis on low coupling, offering a high level of operational isolation. Developers can think of a container as a self-contained process and the PaaS as the common deployment target, using the microservices architecture as the common style. Decoupling the architecture allows the same for teams, cutting down on coordination cost among silos. Its attractiveness to both developers and DevOps has made this the de facto standard for new development in many organizations.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;在 2016 年 11 月份的《&lt;a href=&quot;https://assets.thoughtworks.com/assets/technology-radar-nov-2016-en.pdf&quot;&gt;技术雷达&lt;/a&gt;》中，ThoughtWorks 给予了微服务很高的评价。同时，也有越来越多的组织将实施微服务作为架构演进的一个必选方向。只不过在拥有众多遗留系统的组织内，将曾经的单体系统拆分为微服务并不是一件容易的事情。本文将从对遗留系统进行微服务改造的原则要求出发，探讨如何使用 Dubbo 框架实现单体系统向微服务的迁移。&lt;/p&gt;

&lt;h1 id=&quot;一原则要求&quot;&gt;一、原则要求&lt;/h1&gt;

&lt;p&gt;想要对标准三层架构的单体系统进行微服务改造——简言之——就是将曾经单一进程内服务之间的本地调用改造为跨进程的分布式调用。这虽然不是微服务改造的全部内容，但却直接决定了改造前后的系统能否保持相同的业务能力，以及改造成本的多少。&lt;/p&gt;

&lt;h2 id=&quot;11适合的框架&quot;&gt;1.1、适合的框架&lt;/h2&gt;

&lt;p&gt;在微服务领域，虽然技术栈众多，但无非 RPC 与 RESTful 两个流派，这其中最具影响力的代表当属 &lt;a href=&quot;http://dubbo.io/&quot;&gt;Dubbo&lt;/a&gt; 与 &lt;a href=&quot;http://cloud.spring.io/&quot;&gt;Spring Cloud&lt;/a&gt; 了 。他们拥有相似的能力，却有着截然不同的实现方式——本文并不是想要对微服务框架的选型过程进行深入剖析，也不想对这两种框架的孰优孰劣进行全面比较——本章所提到的全部这些原则要求都是超越具体实现的，其之于任何微服务框架都应该是适用的。读者朋友们大可以把本文中的 Dubbo 全部替换为 Spring Cloud，而并不会对最终结果造成任何影响，唯一需要改变的仅仅是实现的细节过程而已。因此，无论最后抉择如何，都是无所谓对错的，关键在于：要选择符合组织当下现状的最适合的那一个。&lt;/p&gt;

&lt;h2 id=&quot;12方便的将服务暴露为远程接口&quot;&gt;1.2、方便的将服务暴露为远程接口&lt;/h2&gt;

&lt;p&gt;单体系统，服务之间的调用是在同一个进程内完成的；而微服务，是将独立的业务模块拆分到不同的应用系统中，每个应用系统可以作为独立的进程来部署和运行。因此进行微服务改造，就需要将进程内方法调用改造为进程间通信。进程间通信的实现方式有很多种，但显然基于网络调用的方式是最通用且易于实现的。那么能否方便的将本地服务暴露为网络服务，就决定了暴露过程能否被快速实施，同时暴露的过程越简单则暴露后的接口与之前存在不一致性的风险也就越低。&lt;/p&gt;

&lt;h2 id=&quot;13方便的生成远程服务调用代理&quot;&gt;1.3、方便的生成远程服务调用代理&lt;/h2&gt;

&lt;p&gt;当服务被暴露为远程接口以后，进程内的本地实现将不复存在。简化调用方的使用——为远程服务生成相应的本地代理，将底层网络交互细节进行深层次的封装——就显得十分必要。另外远程服务代理在使用与功能上不应该与原有本地实现有任何差别。&lt;/p&gt;

&lt;h2 id=&quot;14保持原有接口不变或向后兼容&quot;&gt;1.4、保持原有接口不变或向后兼容&lt;/h2&gt;

&lt;p&gt;在微服务改造过程中，要确保接口不变或向后兼容，这样才不至于对调用方产生巨大影响。在实际操作过程中，我们有可能仅仅可以掌控被改造的系统，而无法访问或修改调用方系统。倘若接口发生重大变化，调用方系统的维护人员会难以接受：这会对他们的工作产生不可预估的风险和冲击，还会因为适配新接口而产生额外的工作量。&lt;/p&gt;

&lt;h2 id=&quot;15保持原有的依赖注入关系不变&quot;&gt;1.5、保持原有的依赖注入关系不变&lt;/h2&gt;

&lt;p&gt;基于 Spring 开发的遗留系统，服务之间通常是以依赖注入的方式彼此关联的。进行微服务改造后，原本注入的服务实现变成了本地代理，为了尽量减少代码变更，最好能够自动将注入的实现类切换为本地代理。&lt;/p&gt;

&lt;h2 id=&quot;16保持原有代码的作用或副作用效果不变&quot;&gt;1.6、保持原有代码的作用或副作用效果不变&lt;/h2&gt;

&lt;p&gt;这一点看上去有些复杂，但却是必不可少的。改造后的系统跟原有系统保持相同的业务能力，当且仅当改造后的代码与原有代码保持相同的作用甚至是副作用。这里要额外提及的是副作用。我们在改造过程中可以很好的关注一般作用效果，却往往会忽视副作用的影响。举个例子，Java 内部进行方法调用的时候参数是以引用的方式传递的，这意味着在方法体中可以修改参数里的值，并将修改后的结果“返回”给被调用方。看下面的例子会更容易理解：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;innerMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;new&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;outerMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;old&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// {key=old}&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;innerMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;out&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// {key=new}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这段代码在同一个进程中运行是没有问题的，因为两个方法共享同一片内存空间，&lt;code class=&quot;highlighter-rouge&quot;&gt;innerMethod&lt;/code&gt; 对 &lt;code class=&quot;highlighter-rouge&quot;&gt;map&lt;/code&gt; 的修改可以直接反映到 &lt;code class=&quot;highlighter-rouge&quot;&gt;outerMethod&lt;/code&gt; 方法中。但是在微服务场景下事实就并非如此了，此时 &lt;code class=&quot;highlighter-rouge&quot;&gt;innerMethod&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;outerMethod&lt;/code&gt; 运行在两个独立的进程中，进程间的内存相互隔离，&lt;code class=&quot;highlighter-rouge&quot;&gt;innerMethod&lt;/code&gt; 修改的内容必须要主动回传才能被 &lt;code class=&quot;highlighter-rouge&quot;&gt;outerMethod&lt;/code&gt; 接收到，仅仅修改参数里的值是无法达到回传数据的目的的。&lt;/p&gt;

&lt;p&gt;此处副作用的概念是指在方法体中对传入参数的内容进行了修改，并由此对外部上下文产生了可察觉的影响。显然副作用是不友好且应该被避免的，但由于是遗留系统，我们不能保证其中不会存在诸如此类写法的代码，所以我们还是需要在微服务改造过程中，对副作用的影响效果进行保持，以获得更好的兼容性。&lt;/p&gt;

&lt;h2 id=&quot;17尽量少改动最好不改动遗留系统的内部代码&quot;&gt;1.7、尽量少改动（最好不改动）遗留系统的内部代码&lt;/h2&gt;

&lt;p&gt;多数情况下，并非所有遗留系统的代码都是可以被平滑改造的：比如，上面提到的方法具有副作用的情况，以及传入和传出参数为不可序列化对象（未实现 &lt;code class=&quot;highlighter-rouge&quot;&gt;Serializable&lt;/code&gt; 接口）的情况等。我们虽然不能百分之百保证不对遗留系统的代码进行修改，但至少应该保证这些改动被控制在最小范围内，尽量采取变通的方式——例如添加而不是修改代码——这种仅添加的改造方式至少可以保证代码是向后兼容的。&lt;/p&gt;

&lt;h2 id=&quot;18良好的容错能力&quot;&gt;1.8、良好的容错能力&lt;/h2&gt;

&lt;p&gt;不同于进程内调用，跨进程的网络通信可靠性不高，可能由于各种原因而失败。因此在进行微服务改造的时候，远程方法调用需要更多考虑容错能力。当远程方法调用失败的时候，可以进行重试、恢复或者降级，否则不加处理的失败会沿着调用链向上传播（冒泡），从而导致整个系统的级联失败。&lt;/p&gt;

&lt;h2 id=&quot;19改造结果可插拔&quot;&gt;1.9、改造结果可插拔&lt;/h2&gt;

&lt;p&gt;针对遗留系统的微服务改造不可能保证一次性成功，需要不断尝试和改进，这就要求在一段时间内原有代码与改造后的代码并存，且可以通过一些简单的配置让系统在原有模式和微服务模式之间进行无缝切换。优先尝试微服务模式，一旦出现问题可以快速切换回原有模式（手动或自动），循序渐进，直到微服务模式变得稳定。&lt;/p&gt;

&lt;h2 id=&quot;110更多&quot;&gt;1.10、更多&lt;/h2&gt;

&lt;p&gt;当然微服务改造的要求远不止上面提到的这些点，还应该包括诸如：配置管理、服务注册与发现、负载均衡、网关、限流降级、扩缩容、监控和分布式事务等，然而这些需求大部分是要在微服务系统已经升级改造完毕，复杂度不断增加，流量上升到一定程度之后才会遇到和需要的，因此并不是本文关注的重点。但这并不意味着这些内容就不重要，没有他们微服务系统同样也是无法正常、平稳、高速运行的。&lt;/p&gt;

&lt;h1 id=&quot;二模拟一个单体系统&quot;&gt;二、模拟一个单体系统&lt;/h1&gt;

&lt;h2 id=&quot;21系统概述&quot;&gt;2.1、系统概述&lt;/h2&gt;

&lt;p&gt;我们需要构建一个具有三层架构的单体系统来模拟遗留系统，这是一个简单的 Spring Boot 应用，项目名叫做 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-dubbo&lt;/code&gt;。本文涉及到的所有源代码均可以到 &lt;a href=&quot;https://github.com/tangrui/hello-dubbo&quot;&gt;Github&lt;/a&gt; 上查看和下载。&lt;/p&gt;

&lt;p&gt;首先，系统存在一个模型 &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; 和对该模型进行管理的 DAO，并通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService&lt;/code&gt; 向上层暴露访问 &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; 模型的接口；另外，还存在一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;HelloService&lt;/code&gt;，其调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService&lt;/code&gt; 并返回一条问候信息；之后，由 &lt;code class=&quot;highlighter-rouge&quot;&gt;Controller&lt;/code&gt; 对外暴露 RESTful 接口；最终再通过 Spring Boot 的 &lt;code class=&quot;highlighter-rouge&quot;&gt;Application&lt;/code&gt; 整合成一个完整应用。&lt;/p&gt;

&lt;h2 id=&quot;22模块化拆分&quot;&gt;2.2、模块化拆分&lt;/h2&gt;

&lt;p&gt;通常来说，一个具有三层架构的单体系统，其 Controller、Service 和 DAO 是存在于一整个模块内的，如果要进行微服务改造，就要先对这个整体进行拆分。拆分的方法是以 Service 层为分界，将其分割为两个子模块：Service 层往上作为一个子模块（称为 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt;），对外提供 RESTful 接口；Service 层往下作为另外一个子模块（称为 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt;），包括 Service、DAO 以及模型。&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 被 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 依赖。当然，为了更好的体现面向契约的编程精神，可以把 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 再进一步拆分：所有的接口和模型都独立出来，形成 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-api&lt;/code&gt;，而 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 依赖 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-api&lt;/code&gt;。最终，拆分后的模块关系如下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;hello-dubbo
|-- hello-web（包含 Application 和 Controller）
    |-- hello-core（包含 Service 和 DAO 的实现）
        |-- hello-api（包含 Service 和 DAO 的接口以及模型）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;23核心代码分析&quot;&gt;2.3、核心代码分析&lt;/h2&gt;

&lt;h3 id=&quot;231user&quot;&gt;2.3.1、User&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Serializable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createdTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;id&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createdTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createdTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createdTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;createdTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SimpleDateFormat&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sdf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;SimpleDateFormat&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;yyyy-MM-dd HH:mm:ss&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%s (%s)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;sdf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;%s (N/A)&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; 模型是一个标准的 POJO，实现了 &lt;code class=&quot;highlighter-rouge&quot;&gt;Serializable&lt;/code&gt; 接口（因为模型数据要在网络上传输，因此必须能够支持序列化和反序列化）。为了方便控制台输出，这里覆盖了默认的 &lt;code class=&quot;highlighter-rouge&quot;&gt;toString&lt;/code&gt; 方法。&lt;/p&gt;

&lt;h3 id=&quot;232userrepository&quot;&gt;2.3.2、UserRepository&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserRepository&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;UserRepository&lt;/code&gt; 接口是访问 &lt;code class=&quot;highlighter-rouge&quot;&gt;User&lt;/code&gt; 模型的 DAO，为了简单起见，该接口只包含两个方法：&lt;code class=&quot;highlighter-rouge&quot;&gt;getById&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;233inmemoryuserrepository&quot;&gt;2.3.3、InMemoryUserRepository&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Repository&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InMemoryUserRepository&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserRepository&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Map&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;STORE&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HashMap&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;gt;();&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tom&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;tom&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Tom Sawyer&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;tom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;STORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;tom&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;STORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;STORE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(),&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;InMemoryUserRepository&lt;/code&gt; 是 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserRepository&lt;/code&gt; 接口的实现类。该类型使用一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;Map&lt;/code&gt; 对象 &lt;code class=&quot;highlighter-rouge&quot;&gt;STORE&lt;/code&gt; 来存储数据，并通过静态代码块向该对象内添加了一个默认用户。&lt;code class=&quot;highlighter-rouge&quot;&gt;getById&lt;/code&gt; 方法根据 &lt;code class=&quot;highlighter-rouge&quot;&gt;id&lt;/code&gt; 参数从 &lt;code class=&quot;highlighter-rouge&quot;&gt;STORE&lt;/code&gt; 中获取用户数据，而 &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; 方法就是简单将传入的 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象存储到 &lt;code class=&quot;highlighter-rouge&quot;&gt;STORE&lt;/code&gt; 中。由于所有这些操作都只是在内存中完成的，因此该类型被叫做 &lt;code class=&quot;highlighter-rouge&quot;&gt;InMemoryUserRepository&lt;/code&gt;。&lt;/p&gt;

&lt;h3 id=&quot;234userservice&quot;&gt;2.3.4、UserService&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;与 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserRepository&lt;/code&gt; 的方法一一对应，向更上层暴露访问接口。&lt;/p&gt;

&lt;h3 id=&quot;235defaultuserservice&quot;&gt;2.3.5、DefaultUserService&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userService&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultUserService&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Logger&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LOGGER&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;LoggerFactory&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getLogger&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;DefaultUserService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserRepository&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LOGGER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LOGGER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;DefaultUserService&lt;/code&gt; 是 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService&lt;/code&gt; 接口的默认实现，并通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Service&lt;/code&gt; 注解声明为一个服务，服务 id 为 &lt;code class=&quot;highlighter-rouge&quot;&gt;userService&lt;/code&gt;（该 id 在后面会需要用到）。该服务内部注入了一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserRepository&lt;/code&gt; 类型的对象 &lt;code class=&quot;highlighter-rouge&quot;&gt;userRepository&lt;/code&gt;。&lt;code class=&quot;highlighter-rouge&quot;&gt;getUserById&lt;/code&gt; 方法根据 &lt;code class=&quot;highlighter-rouge&quot;&gt;id&lt;/code&gt; 从 &lt;code class=&quot;highlighter-rouge&quot;&gt;userRepository&lt;/code&gt; 中获取数据，而 &lt;code class=&quot;highlighter-rouge&quot;&gt;createUser&lt;/code&gt; 方法则将传入的 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 参数通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;userRepository.create&lt;/code&gt; 方法存入，并在存入之前设置了该对象的创建时间。很显然，根据 1.6 节关于副作用的描述，为 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象设置创建时间的操作就属于具有副作用的操作，需要在微服务改造之后加以保留。为了方便看到系统工作效果，这两个方法里面都打印了日志。&lt;/p&gt;

&lt;h3 id=&quot;236helloservice&quot;&gt;2.3.6、HelloService&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;HelloService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;HelloService&lt;/code&gt; 接口只提供一个方法 &lt;code class=&quot;highlighter-rouge&quot;&gt;sayHello&lt;/code&gt;，就是根据传入的 &lt;code class=&quot;highlighter-rouge&quot;&gt;userId&lt;/code&gt; 返回一条对该用户的问候信息。&lt;/p&gt;

&lt;h3 id=&quot;237defaulthelloservice&quot;&gt;2.3.7、DefaultHelloService&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;helloService&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultHelloService&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HelloService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;format&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Hello, %s.&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;DefaultHelloService&lt;/code&gt; 是 &lt;code class=&quot;highlighter-rouge&quot;&gt;HelloService&lt;/code&gt; 接口的默认实现，并通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Service&lt;/code&gt; 注解声明为一个服务，服务 id 为 &lt;code class=&quot;highlighter-rouge&quot;&gt;helloService&lt;/code&gt;（同样，该名称在后面的改造过程中会被用到）。该类型内部注入了一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService&lt;/code&gt; 类型的对象 &lt;code class=&quot;highlighter-rouge&quot;&gt;userService&lt;/code&gt;。&lt;code class=&quot;highlighter-rouge&quot;&gt;sayHello&lt;/code&gt; 方法根据 &lt;code class=&quot;highlighter-rouge&quot;&gt;userId&lt;/code&gt; 参数通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;userService&lt;/code&gt; 获取用户信息，并返回一条经过格式化后的消息。&lt;/p&gt;

&lt;h3 id=&quot;238application&quot;&gt;2.3.8、Application&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@SpringBootApplication&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SpringApplication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Application&lt;/code&gt; 类型是 Spring Boot 应用的入口，详细描述请参考 Spring Boot 的&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/1.5.10.RELEASE/reference/htmlsingle/&quot;&gt;官方文档&lt;/a&gt;，在此不详细展开。&lt;/p&gt;

&lt;h3 id=&quot;239controller&quot;&gt;2.3.9、Controller&lt;/h3&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HelloService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;helloService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;/hello/{userId}&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@PathVariable&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userId&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;helloService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;sayHello&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/create&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@RequestParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userId&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@RequestParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Controller&lt;/code&gt; 类型是一个标准的 Spring MVC Controller，在此不详细展开讨论。仅仅需要说明的是这个类型注入了 &lt;code class=&quot;highlighter-rouge&quot;&gt;HelloService&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService&lt;/code&gt; 类型的对象，并在 &lt;code class=&quot;highlighter-rouge&quot;&gt;sayHello&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;createUser&lt;/code&gt; 方法中调用了这两个对象中的有关方法。&lt;/p&gt;

&lt;h2 id=&quot;24打包运行&quot;&gt;2.4、打包运行&lt;/h2&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-dubbo&lt;/code&gt; 项目包含三个子模块：&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-api&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt;，是用 Maven 来管理的。到目前为止所涉及到的 POM 文件都比较简单，为了节约篇幅，就不在此一一列出了，感兴趣的朋友可以到项目的 Github 仓库上自行研究。&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-dubbo&lt;/code&gt; 项目的打包和运行都非常直接：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 编译、打包和安装&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 在项目根目录下执行命令&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mvn clean install

&lt;span class=&quot;c&quot;&gt;# 运行&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# 在 hello-web 目录下执行命令&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mvn spring-boot:run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;测试结果如下，注意每次输出括号里面的日期时间，它们都应该是有值的。&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/dubbo/test-hello-web.png&quot; alt=&quot;样例系统测试结果&quot; /&gt;&lt;/p&gt;

&lt;p&gt;再返回 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 系统的控制台，查看一下日志输出，时间应该与上面是一样的。&lt;br /&gt;
&lt;img src=&quot;/static/uploads/2017/dubbo/hello-web-console.png&quot; alt=&quot;样例系统控制台&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;三动手改造&quot;&gt;三、动手改造&lt;/h1&gt;

&lt;h2 id=&quot;31改造目标&quot;&gt;3.1、改造目标&lt;/h2&gt;

&lt;p&gt;上一章，我们已经成功构建了一个模拟系统，该系统是一个单体系统，对外提供了两个 RESTful 接口。本章要达到的目标是将该单体系统拆分为两个独立运行的微服务系统。如 2.2 节所述，进行模块化拆分是实施微服务改造的重要一步，因为在接下来的描述中会暗含一个约定：&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-api&lt;/code&gt; 这三个模块与上一章中所设定的能力是相同的。基于 1.7 节所提到的“尽量少改动（最好不改动）遗留系统的内部代码”的改造要求，这三个模块中的代码是不会被大面积修改的，只会有些许调整，以适应新的微服务环境。&lt;/p&gt;

&lt;p&gt;具体将要实现的目标效果如下：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;第一个微服务系统：

hello-web（包含 Application 和 Controller）
|-- hello-service-reference（包含 Dubbo 有关服务引用的配置）
    |-- hello-api（包含 Service 和 DAO 的接口以及模型）


第二个微服务系统：

hello-service-provider（包含 Dubbo 有关服务暴露的配置）
|-- hello-core（包含 Service 和 DAO 的实现）
    |-- hello-api（包含 Service 和 DAO 的接口以及模型）
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 与原来一样，是一个面向最终用户提供 Web 服务的终端系统，其只包含 Application、Controller、Service 接口、 DAO 接口以及模型，因此它本身是不具备任何业务能力的，必须通过依赖 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 模块来远程调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 系统才能完成业务。而 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 系统则需要暴露可供 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 模块调用的远程接口，并实现 Service 及 DAO 接口定义的具体业务逻辑。&lt;/p&gt;

&lt;p&gt;本章节就是要重点介绍 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 模块是如何构建的，以及它们在微服务改造过程中所起到的作用。&lt;/p&gt;

&lt;h2 id=&quot;32暴露远程服务&quot;&gt;3.2、暴露远程服务&lt;/h2&gt;

&lt;p&gt;Spring Boot 和 Dubbo 的结合使用可以引入诸如 &lt;code class=&quot;highlighter-rouge&quot;&gt;spring-boot-starter-dubbo&lt;/code&gt; 这样的&lt;a href=&quot;https://github.com/alibaba/dubbo-spring-boot-starter&quot;&gt;起始包&lt;/a&gt;，使用起来会更加方便。但是考虑到项目的单纯性和通用性，本文仍然延用 Spring 经典的方式进行配置。&lt;/p&gt;

&lt;p&gt;首先，我们需要创建一个新的模块，叫做 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt;，这个模块的作用是用来暴露远程服务接口的。依托于 Dubbo 强大的服务暴露及整合能力，该模块不用编写任何代码，仅需添加一些配置即可完成。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注：有关 Dubbo 的具体使用和配置说明并不是本文讨论的重点，请参考官方文档。&lt;/strong&gt;&lt;/p&gt;

&lt;h3 id=&quot;321添加-dubbo-servicesxml-文件&quot;&gt;3.2.1、添加 dubbo-services.xml 文件&lt;/h3&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;dubbo-services.xml&lt;/code&gt; 配置是该模块的关键，Dubbo 就是根据这个文件，自动暴露远程服务的。这是一个标准 Spring 风格的配置文件，引入了 Dubbo 命名空间，需要将其摆放在 &lt;code class=&quot;highlighter-rouge&quot;&gt;src/main/resources/META-INF/spring&lt;/code&gt; 目录下，这样 Maven 在打包的时候会自动将其添加到 classpath。&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;beans&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.springframework.org/schema/beans&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns:context=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.springframework.org/schema/context&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns:xsi=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns:dubbo=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://code.alibabatech.com/schema/dubbo&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;
  http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
  http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
  http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 检索 net.tangrui.demo.dubbo.hello 包下的所有类型，并应用注解  --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;context:component-scan&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;base-package=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;net.tangrui.demo.dubbo.hello&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 定义该 Dubbo 应用的名称 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:application&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello-service-provider&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!--
    - 使用广播机制进行服务注册与发现
    - 基于广播机制实现的服务注册与发现仅限于开发过程使用，因为其无需搭建和启动任何其他服务
    - 如果是生产环境请使用 Zookeeper 或 Redis
    - 详细内容请参考 Dubbo 文档
    --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:registry&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;address=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;multicast://224.5.6.7:1234&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 定义 RPC 传输协议 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:protocol&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dubbo&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!--
    - 暴露 UserService 服务
    - 这里服务实现使用 ref 属性指定，属性值 userService 即为 DefaultUserService 上的注解 @Service 中指定的 id
    --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:service&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;interface=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;net.tangrui.demo.dubbo.hello.service.UserService&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ref=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userService&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 暴露 HelloService 服务 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:service&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;interface=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;net.tangrui.demo.dubbo.hello.service.HelloService&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ref=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;helloService&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/beans&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;322添加-pom-文件&quot;&gt;3.2.2、添加 POM 文件&lt;/h3&gt;

&lt;p&gt;有关 Maven 的使用与配置也不是本文关注的重点，但是这个模块用到了一些 Maven 插件，在此对这些插件的功能和作用进行一下描述。&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;project&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://maven.apache.org/POM/4.0.0&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns:xsi=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;modelVersion&amp;gt;&lt;/span&gt;4.0.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/modelVersion&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;parent&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;net.tangrui&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;hello-dubbo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.1.0-SNAPSHOT&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/parent&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;hello-service-provider&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;packaging&amp;gt;&lt;/span&gt;jar&lt;span class=&quot;nt&quot;&gt;&amp;lt;/packaging&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Hello Service Provider&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;properties&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;slf4j.version&amp;gt;&lt;/span&gt;1.7.25&lt;span class=&quot;nt&quot;&gt;&amp;lt;/slf4j.version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;logback.version&amp;gt;&lt;/span&gt;1.2.3&lt;span class=&quot;nt&quot;&gt;&amp;lt;/logback.version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo.version&amp;gt;&lt;/span&gt;2.6.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dubbo.version&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/properties&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 需要依赖服务的具体实现 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;net.tangrui&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;hello-core&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${project.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 引入日志组件 --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.slf4j&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;slf4j-api&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${slf4j.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.slf4j&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jcl-over-slf4j&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${slf4j.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.slf4j&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;jul-to-slf4j&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${slf4j.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.slf4j&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;log4j-over-slf4j&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${slf4j.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;ch.qos.logback&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;logback-classic&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${logback.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 引入 Dubbo --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.alibaba&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;dubbo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${dubbo.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;plugins&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 从依赖包中提取文件 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-dependency-plugin&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.0.2&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 在 package 阶段对指定的依赖包进行解压缩 --&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;unpack&lt;span class=&quot;nt&quot;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class=&quot;nt&quot;&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;unpack&lt;span class=&quot;nt&quot;&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactItems&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactItem&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 指定依赖包的 groupId, artifactId 和 version --&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.alibaba&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;dubbo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${dubbo.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 提取依赖包中 META-INF/assembly 目录下的所有内容（主要是可执行脚本） --&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;includes&amp;gt;&lt;/span&gt;META-INF/assembly/**&lt;span class=&quot;nt&quot;&gt;&amp;lt;/includes&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 输出到指定目录 --&amp;gt;&lt;/span&gt;
                  &lt;span class=&quot;nt&quot;&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;${project.build.directory}/dubbo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
                &lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactItem&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactItems&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;

      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 根据 assembly.xml 文件的配置将此项目重新打包 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-assembly-plugin&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;3.1.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 指定 assembly.xml 文件的路径 --&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;descriptors&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;descriptor&amp;gt;&lt;/span&gt;src/main/assembly/assembly.xml&lt;span class=&quot;nt&quot;&gt;&amp;lt;/descriptor&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/descriptors&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 在 package 阶段进行打包 --&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;make-assembly&lt;span class=&quot;nt&quot;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;phase&amp;gt;&lt;/span&gt;package&lt;span class=&quot;nt&quot;&gt;&amp;lt;/phase&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;single&lt;span class=&quot;nt&quot;&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;

      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 在 Maven 中直接启动应用 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.codehaus.mojo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;exec-maven-plugin&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.6.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;executions&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;execution&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;goals&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 执行 java 命令 --&amp;gt;&lt;/span&gt;
              &lt;span class=&quot;nt&quot;&gt;&amp;lt;goal&amp;gt;&lt;/span&gt;java&lt;span class=&quot;nt&quot;&gt;&amp;lt;/goal&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;/goals&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/execution&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/executions&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 执行指定类型下的 main 方法 --&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;mainClass&amp;gt;&lt;/span&gt;com.alibaba.dubbo.container.Main&lt;span class=&quot;nt&quot;&gt;&amp;lt;/mainClass&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 向 classpath 添加额外路径，主要包含配置文件 --&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;additionalClasspathElements&amp;gt;&lt;/span&gt;
            &lt;span class=&quot;nt&quot;&gt;&amp;lt;additionalClasspathElement&amp;gt;&lt;/span&gt;src/main/assembly/conf&lt;span class=&quot;nt&quot;&gt;&amp;lt;/additionalClasspathElement&amp;gt;&lt;/span&gt;
          &lt;span class=&quot;nt&quot;&gt;&amp;lt;/additionalClasspathElements&amp;gt;&lt;/span&gt;
        &lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;323添加-assemblyxml-文件&quot;&gt;3.2.3、添加 assembly.xml 文件&lt;/h3&gt;

&lt;p&gt;Assembly 插件的主要功能是对项目重新打包，以便自定义打包方式和内容。对本项目而言，需要生成一个压缩包，里面包含所有运行该服务所需要的 jar 包、配置文件和启动脚本等。Assembly 插件需要 &lt;code class=&quot;highlighter-rouge&quot;&gt;assembly.xml&lt;/code&gt; 文件来描述具体的打包过程，该文件需要摆放在 &lt;code class=&quot;highlighter-rouge&quot;&gt;src/main/assembly&lt;/code&gt; 目录下。有关 &lt;code class=&quot;highlighter-rouge&quot;&gt;assembly.xml&lt;/code&gt; 文件的具体配置方法，请参考&lt;a href=&quot;http://maven.apache.org/plugins/maven-assembly-plugin/assembly.html&quot;&gt;官方文档&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;assembly&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://maven.apache.org/ASSEMBLY/2.0.0&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns:xsi=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;id&amp;gt;&lt;/span&gt;assembly&lt;span class=&quot;nt&quot;&gt;&amp;lt;/id&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;formats&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 打包成 tar.gz 格式 --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;format&amp;gt;&lt;/span&gt;tar.gz&lt;span class=&quot;nt&quot;&gt;&amp;lt;/format&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/formats&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 让压缩包包含基础根目录 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;includeBaseDirectory&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/includeBaseDirectory&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;fileSets&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;c&quot;&gt;&amp;lt;!--
      - 将 com.alibaba:dubbo 依赖包里面解压出来的文件重新打包
      - 从该依赖包里面解压文件的操作是在 POM 的 maven-dependency-plugin 插件中描述的
      --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;fileSet&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 欲打包的目录 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;${project.build.directory}/dubbo/META-INF/assembly/bin&lt;span class=&quot;nt&quot;&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 打包后的输出目录 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;bin&lt;span class=&quot;nt&quot;&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 指定文件权限，主要是给予可执行权限 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;fileMode&amp;gt;&lt;/span&gt;0755&lt;span class=&quot;nt&quot;&gt;&amp;lt;/fileMode&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/fileSet&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 打包配置文件 --&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;fileSet&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 配置文件所在目录 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;directory&amp;gt;&lt;/span&gt;src/main/assembly/conf&lt;span class=&quot;nt&quot;&gt;&amp;lt;/directory&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 打包后的输出目录 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;conf&lt;span class=&quot;nt&quot;&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 指定文件权限，没有可执行权限 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;fileMode&amp;gt;&lt;/span&gt;0644&lt;span class=&quot;nt&quot;&gt;&amp;lt;/fileMode&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/fileSet&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/fileSets&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 提取本项目的所有依赖包 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependencySets&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependencySet&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 输出到 lib 目录下 --&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;outputDirectory&amp;gt;&lt;/span&gt;lib&lt;span class=&quot;nt&quot;&gt;&amp;lt;/outputDirectory&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependencySet&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependencySets&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/assembly&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;324添加-logbackxml-文件&quot;&gt;3.2.4、添加 logback.xml 文件&lt;/h3&gt;

&lt;p&gt;由于在 POM 文件中指定了使用 logback 作为日志输出组件，因此还需要在 &lt;code class=&quot;highlighter-rouge&quot;&gt;logback.xml&lt;/code&gt; 文件中对其进行配置。该文件需要摆放在 &lt;code class=&quot;highlighter-rouge&quot;&gt;src/main/resources&lt;/code&gt; 目录下，有关该配置文件的具体内容请参见代码仓库，有关配置的详细解释，请参考&lt;a href=&quot;https://logback.qos.ch/manual/configuration.html&quot;&gt;官方文档&lt;/a&gt;。&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;configuration&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;debug=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;true&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;property&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;applicationName&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello-service-provider&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;contextName&amp;gt;&lt;/span&gt;${applicationName}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/contextName&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;appender&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;STDOUT&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;ch.qos.logback.core.ConsoleAppender&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;encoder&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;pattern&amp;gt;&lt;/span&gt;
        %d{yyyy-MM-dd hh:mm:ss.SSS} %-5p %logger.%M [%t] - %m%n
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;/pattern&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/encoder&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/appender&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;root&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;level=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;INFO&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;appender-ref&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;ref=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;STDOUT&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/root&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;325打包&quot;&gt;3.2.5、打包&lt;/h3&gt;

&lt;p&gt;由于已经在 POM 文件中定义了打包的相关配置，因此直接在 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 目录下运行以下命令即可：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;mvn clean package
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;成功执行以后，会在其 target 目录下生成一个名为 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider-0.1.0-SNAPSHOT-assembly.tar.gz&lt;/code&gt; 的压缩包，里面的内容如图所示：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/assembly.png&quot; alt=&quot;压缩包里面的内容&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;326运行&quot;&gt;3.2.6、运行&lt;/h3&gt;

&lt;p&gt;如此配置完成以后，就可以使用如下命令来启动服务：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;MAVEN_OPTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-Djava.net.preferIPv4Stack=true&quot;&lt;/span&gt; mvn &lt;span class=&quot;nb&quot;&gt;exec&lt;/span&gt;:java
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;注：在 macOS 系统里，使用 multicast 机制进行服务注册与发现，需要添加 &lt;code class=&quot;highlighter-rouge&quot;&gt;-Djava.net.preferIPv4Stack=true&lt;/code&gt; 参数，否则会抛出异常。&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;可以使用如下命令来判断服务是否正常运行：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;netstat &lt;span class=&quot;nt&quot;&gt;-antl&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;20880
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;如果有类似如下的信息输出，则说明运行正常。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/netstat.png&quot; alt=&quot;监听 20880 端口&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如果是在正式环境运行，就需要将上一步生成的压缩包解压，然后运行 &lt;code class=&quot;highlighter-rouge&quot;&gt;bin&lt;/code&gt; 目录下的相应脚本即可。&lt;/p&gt;

&lt;h3 id=&quot;327总结&quot;&gt;3.2.7、总结&lt;/h3&gt;

&lt;p&gt;使用这种方式来暴露远程服务具有如下一些优势：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;👍 使用 Dubbo 进行远程服务暴露，无需关注底层实现细节&lt;/li&gt;
  &lt;li&gt;👍 对原系统没有任何入侵，已有系统可以继续按照原来的方式启动和运行&lt;/li&gt;
  &lt;li&gt;👍 暴露过程可插拔&lt;/li&gt;
  &lt;li&gt;👍 Dubbo 服务与原有服务在开发期和运行期均可以共存&lt;/li&gt;
  &lt;li&gt;👍 无需编写任何代码&lt;/li&gt;
&lt;/ol&gt;

&lt;h2 id=&quot;33引用远程服务&quot;&gt;3.3、引用远程服务&lt;/h2&gt;

&lt;h3 id=&quot;331添加服务引用&quot;&gt;3.3.1、添加服务引用&lt;/h3&gt;

&lt;p&gt;与 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 模块的处理方式相同，为了不侵入原有系统，我们创建另外一个模块，叫做 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt;。这个模块只有一个配置文件 &lt;code class=&quot;highlighter-rouge&quot;&gt;dubbo-references.xml&lt;/code&gt; 放置在 &lt;code class=&quot;highlighter-rouge&quot;&gt;src/main/resources/META-INF/spring/&lt;/code&gt; 目录下。文件的内容非常简单明了：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;beans&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.springframework.org/schema/beans&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns:xsi=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns:dubbo=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://code.alibabatech.com/schema/dubbo&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;
        http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
        http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 定义该 Dubbo 应用的名称 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:application&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;hello-service-reference&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 使用广播机制进行服务注册与发现，注意需要跟 hello-service-provider 使用相同的广播地址 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:registry&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;address=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;multicast://224.5.6.7:1234&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 定义 RPC 传输协议 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:protocol&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;dubbo&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 引用 hello-service-provider 暴露出来的 UserService 服务 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:reference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userServiceReference&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;interface=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;net.tangrui.demo.dubbo.hello.service.UserService&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 引用 hello-service-provider 暴露出来的 HelloService 服务 --&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:reference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;helloServiceReference&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;interface=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;net.tangrui.demo.dubbo.hello.service.HelloService&quot;&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/beans&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;但不同于 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 模块的一点在于，该模块只需要打包成一个 jar 即可，POM 文件内容如下：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;cp&quot;&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot;?&amp;gt;&lt;/span&gt;

&lt;span class=&quot;nt&quot;&gt;&amp;lt;project&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://maven.apache.org/POM/4.0.0&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xmlns:xsi=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://www.w3.org/2001/XMLSchema-instance&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;modelVersion&amp;gt;&lt;/span&gt;4.0.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/modelVersion&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;parent&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;net.tangrui&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;hello-dubbo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.1.0-SNAPSHOT&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/parent&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;hello-service-reference&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;packaging&amp;gt;&lt;/span&gt;jar&lt;span class=&quot;nt&quot;&gt;&amp;lt;/packaging&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;name&amp;gt;&lt;/span&gt;Hello Service Reference&lt;span class=&quot;nt&quot;&gt;&amp;lt;/name&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;properties&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo.version&amp;gt;&lt;/span&gt;2.6.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dubbo.version&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/properties&amp;gt;&lt;/span&gt;

  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;net.tangrui&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;hello-api&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${project.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

    &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.alibaba&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;dubbo&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${dubbo.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;总结一下，我们曾经的遗留系统分为三个模块 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-api&lt;/code&gt;。经过微服务化处理以后，&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-api&lt;/code&gt; 被剥离了出去，加上 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 模块，形成了一个可以独立运行的 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 系统，因此需要打包成一个完整的应用；而 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 要想调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 提供的服务，就不能再直接依赖 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 模块了，而是需要依赖我们这里创建的 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 模块，因此 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 是作为一个依赖库出现的，其目的就是远程调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 暴露出来的服务，并提供本地代理。&lt;/p&gt;

&lt;p&gt;这时 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 模块的依赖关系就发生了变化：原来 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 模块直接依赖 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt;，再通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 间接依赖 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-api&lt;/code&gt;，而现在我们需要将其改变为直接依赖 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 模块，再通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 模块间接依赖 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-api&lt;/code&gt;。改造前后的依赖关系分别为：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 改造前 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
  ...
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;net.tangrui&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;hello-core&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${project.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
  ...
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 改造后 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependencies&amp;gt;&lt;/span&gt;
  ...
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.yunzhijia&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;hello-service-reference&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${project.version}&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
  ...
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependencies&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;332启动服务&quot;&gt;3.3.2、启动服务&lt;/h3&gt;

&lt;p&gt;因为是测试环境，只需要执行以下命令即可，但在进行本操作之前，需要先启动 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 服务。&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;MAVEN_OPTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-Djava.net.preferIPv4Stack=true&quot;&lt;/span&gt; mvn spring-boot:run
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Oops！系统并不能像期望的那样正常运行，会抛出如下异常：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/failed-to-start.png&quot; alt=&quot;启动失败&quot; /&gt;&lt;/p&gt;

&lt;p&gt;意思是说 &lt;code class=&quot;highlighter-rouge&quot;&gt;net.tangrui.demo.dubbo.hello.web.Controller&lt;/code&gt; 这个类的 &lt;code class=&quot;highlighter-rouge&quot;&gt;helloService&lt;/code&gt; 字段需要一个类型为  &lt;code class=&quot;highlighter-rouge&quot;&gt;net.tangrui.demo.dubbo.hello.service.HelloService&lt;/code&gt; 的 Bean，但是没有找到。相关代码片段如下：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;HelloService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;helloService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Autowired&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;显然，&lt;code class=&quot;highlighter-rouge&quot;&gt;helloService&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;userService&lt;/code&gt; 都是无法注入的，这是为什么呢？&lt;/p&gt;

&lt;p&gt;原因自然跟我们修改 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 这个模块的依赖关系有关。原本 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 是依赖于 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 的，&lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 里面声明了 &lt;code class=&quot;highlighter-rouge&quot;&gt;HelloService&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService&lt;/code&gt; 这两个服务（通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Service&lt;/code&gt; 注解），然后 &lt;code class=&quot;highlighter-rouge&quot;&gt;Controller&lt;/code&gt; 在 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Autowired&lt;/code&gt; 的时候就可以自动绑定了。但是，现在我们将 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-core&lt;/code&gt; 替换成了 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt;，在 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 的配置文件中声明了两个对远程服务的引用，按道理来说这个注入应该是可以生效的，但显然实际情况并非如此。&lt;/p&gt;

&lt;p&gt;仔细思考不难发现，我们在执行 &lt;code class=&quot;highlighter-rouge&quot;&gt;mvn exec:java&lt;/code&gt; 命令启动 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 模块的时候指定了启动 &lt;code class=&quot;highlighter-rouge&quot;&gt;com.alibaba.dubbo.container.Main&lt;/code&gt; 类型，然后才会开始启动并加载 Dubbo 的有关配置，这一点从日志中可以得到证实（日志里面会打印出来很多带有 &lt;code class=&quot;highlighter-rouge&quot;&gt;[DUBBO]&lt;/code&gt; 标签的内容），显然在这次运行中，我们并没有看到类似这样的日志，说明 Dubbo 在这里没有被正确启动。归根结底还是 Spring Boot 的原因，即 Spring Boot 需要一些配置才能够正确加载和启动 Dubbo。&lt;/p&gt;

&lt;p&gt;让 Spring Boot 支持 Dubbo 有很多种方法，比如前面提到的 &lt;code class=&quot;highlighter-rouge&quot;&gt;spring-boot-starter-dubbo&lt;/code&gt; 起始包，但这里同样为了简单和通用，我们依旧采用经典的方式来解决。&lt;/p&gt;

&lt;p&gt;继续思考，该模块没有成功启动 Dubbo，仅仅是因为添加了对 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 的引用，而 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 模块就只有一个文件 &lt;code class=&quot;highlighter-rouge&quot;&gt;dubbo-references.xml&lt;/code&gt;，这就说明 Spring Boot 并没有加载到这个文件。顺着这个思路，只需要让 Spring Boot 能够成功加载这个文件，问题就可以了。Spring Boot 也确实提供了这样的能力，只可惜无法完全做到代码无侵入，只能说这些改动是可以被接受的。修改方式是替换 &lt;code class=&quot;highlighter-rouge&quot;&gt;Application&lt;/code&gt; 中的注解（至于为什么要修改成这样的结果，超出了本文的讨论范围，请自行 Google）。&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Configuration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@EnableAutoConfiguration&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@ComponentScan&lt;/span&gt;
&lt;span class=&quot;nd&quot;&gt;@ImportResource&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;classpath:META-INF/spring/dubbo-references.xml&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Application&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;static&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;SpringApplication&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;args&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这里的主要改动，是将一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;@SpringBootApplication&lt;/code&gt; 注解替换为 &lt;code class=&quot;highlighter-rouge&quot;&gt;@Configuration&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;@EnableAutoConfiguration&lt;/code&gt;、&lt;code class=&quot;highlighter-rouge&quot;&gt;@ComponentScan&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;@ImportResource&lt;/code&gt; 四个注解。不难看出，最后一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;@ImportResource&lt;/code&gt; 就是我们需要的。&lt;/p&gt;

&lt;p&gt;这时再重新尝试启动，就一切正常了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/hello-tom.png&quot; alt=&quot;访问 http://127.0.0.1:8080/hello/tom&quot; /&gt;&lt;/p&gt;

&lt;p&gt;但是，我们如何验证结果确实是从 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 服务过来的呢？这时就需要用到 &lt;code class=&quot;highlighter-rouge&quot;&gt;DefaultUserService&lt;/code&gt; 里面的那几行日志输出了，回到 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 服务的控制台，能够看到类似这样的输出：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/here-it-is.png&quot; alt=&quot;HERE IT IS&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如此便可以确信系统的拆分是被成功实现了。再试试创建用户的接口：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;curl &lt;span class=&quot;nt&quot;&gt;-X&lt;/span&gt; POST &lt;span class=&quot;s1&quot;&gt;'http://127.0.0.1:8080/create?userId=huckleberry&amp;amp;name=Huckleberry%20Finn'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/create-user-failed.png&quot; alt=&quot;创建用户失败&quot; /&gt;&lt;/p&gt;

&lt;p&gt;等等，什么！括号里面的创建时间为什么是 &lt;code class=&quot;highlighter-rouge&quot;&gt;N/A&lt;/code&gt;，这说明 &lt;code class=&quot;highlighter-rouge&quot;&gt;createdTime&lt;/code&gt; 字段根本没有值！&lt;/p&gt;

&lt;h2 id=&quot;34保持副作用效果&quot;&gt;3.4、保持副作用效果&lt;/h2&gt;

&lt;p&gt;让我们先来回顾一下 1.6 节所提到的副作用效果。在 &lt;code class=&quot;highlighter-rouge&quot;&gt;DefaultUserService.create&lt;/code&gt; 方法中，我们为传入的 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 参数设置了创建时间，这一操作就是我们要关注的具有副作用效果的操作。&lt;/p&gt;

&lt;p&gt;先说单体系统的情况。单体系统是运行在一个 Java 虚拟机中的，所有对象共享一片内存空间，彼此可以互相访问。系统在运行的时候，先是由 &lt;code class=&quot;highlighter-rouge&quot;&gt;Controller.create&lt;/code&gt; 方法获取用户输入，将输入的参数封装为一个 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象，再传递给 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService.create&lt;/code&gt; 方法（具体是在调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;DefaultUserService.create&lt;/code&gt; 方法），这时 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象的 &lt;code class=&quot;highlighter-rouge&quot;&gt;createdTime&lt;/code&gt; 字段就被设置了。由于 Java 是以引用的方式来传递参数，因此在 &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; 方法中对 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象所做的变更，是能够反映到调用方那里的——即 &lt;code class=&quot;highlighter-rouge&quot;&gt;Controller.create&lt;/code&gt; 方法里面也是可以获取到变更的，所以返回给用户的时候，这个 &lt;code class=&quot;highlighter-rouge&quot;&gt;createdTime&lt;/code&gt; 就是存在的。&lt;/p&gt;

&lt;p&gt;再说微服务系统的情况。此时系统是独立运行在两个虚拟机中的，彼此之间的内存是相互隔离的。起始点同样是 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 系统的 &lt;code class=&quot;highlighter-rouge&quot;&gt;Controller.create&lt;/code&gt; 方法：获取用户输入，封装 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象。可是在调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService.create&lt;/code&gt; 方法的时候，并不是直接调用 &lt;code class=&quot;highlighter-rouge&quot;&gt;DefaultUserService&lt;/code&gt; 中的方法，而是调用了一个具有相同接口的本地代理，这个代理将 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象序列化之后，通过网络传输给了 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 系统。该系统接收到数据以后，先进行反序列化，生成跟原来对象一模一样的副本，再由 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService.create&lt;/code&gt; 方法进行处理（这回调用的就是 &lt;code class=&quot;highlighter-rouge&quot;&gt;DefaultUserService&lt;/code&gt; 里面的实现了）。至此，这个被设置过 &lt;code class=&quot;highlighter-rouge&quot;&gt;createdTime&lt;/code&gt; 的 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象副本是一直存在于 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 系统的内存里面的，从来没有被传递出去，自然是无法被 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 系统读取到的，所以最终打印出来的结果，括号里面的内容就是 &lt;code class=&quot;highlighter-rouge&quot;&gt;N/A&lt;/code&gt; 了。记得我们有在 &lt;code class=&quot;highlighter-rouge&quot;&gt;DefaultUserService.create&lt;/code&gt; 方法中输出过日志，所以回到 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-provider&lt;/code&gt; 系统的控制台，可以看到如下的日志信息，说明在这个系统里面 &lt;code class=&quot;highlighter-rouge&quot;&gt;createdTime&lt;/code&gt; 字段确实是有值的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/create-user-failed-provider-log.png&quot; alt=&quot;服务提供方的日志&quot; /&gt;&lt;/p&gt;

&lt;p&gt;那么该如何让这个副作用效果也能够被处于另外一个虚拟机中的 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 系统感知到呢，方法只有一种，就是将变更后的数据回传。&lt;/p&gt;

&lt;h3 id=&quot;341为方法添加返回值&quot;&gt;3.4.1、为方法添加返回值&lt;/h3&gt;

&lt;p&gt;这是最容易想到的一种实现方式，简单的说就是修改服务接口，将变更后的数据返回。&lt;/p&gt;

&lt;p&gt;首先，修改 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService&lt;/code&gt; 接口的 &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; 方法，添加返回值：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 为方法添加返回值&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后，修改实现类中相应的方法，将变更后的 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 对象返回：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userService&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultUserService&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LOGGER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 将变更后的数据返回&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后，修改调用方实现，接收返回值：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@RestController&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Controller&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@RequestMapping&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;path&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;&quot;/create&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;method&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;RequestMethod&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;POST&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nd&quot;&gt;@RequestParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userId&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nd&quot;&gt;@RequestParam&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;userId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// 接收返回值&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;编译、运行并测试（如下图），正如我们所期望的，括号中的创建时间又回来了。其工作原理与本节开始时所描述的是一样的，只是方向相反而已。在此不再详细展开，留给大家自行思考。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/create-user-worked-again.png&quot; alt=&quot;创建用户功能又可以正常工作了&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种修改方式有如下一些优缺点：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;👍 方法简单，容易理解&lt;/li&gt;
  &lt;li&gt;👎 改变了系统接口，且改变后的接口与原有接口不兼容（违背了 1.4 节关于“保持原有接口不变或向后兼容”原则的要求）&lt;/li&gt;
  &lt;li&gt;👎 由此也不可避免的造成了对遗留系统内部代码的修改（违背了 1.7 节关于“尽量少改动（最好不改动）遗留系统的内部代码”原则的要求）&lt;/li&gt;
  &lt;li&gt;👎 修改方式不可插拔（违背了 1.9 节“改造结果可插拔”原则的要求）&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;由此可见，这种改法虽然简单，却是利大于弊的，除非我们能够完全掌控整个系统，否则这种修改方式的风险会随着系统复杂性的增加而急剧上升。&lt;/p&gt;

&lt;h3 id=&quot;342添加一个新方法&quot;&gt;3.4.2、添加一个新方法&lt;/h3&gt;

&lt;p&gt;如果不能做到不改变接口，那我们至少要做到改变后的接口与原有接口向后兼容。保证向后兼容性的一种解决办法，就是不改变原有方法，而是添加一个新的方法。过程如下：&lt;/p&gt;

&lt;p&gt;首先，为 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserService&lt;/code&gt; 接口添加一个新的方法 &lt;code class=&quot;highlighter-rouge&quot;&gt;__rpc_create&lt;/code&gt;。这个方法名虽然看起来有些奇怪，但却有两点好处：第一、不会和已有方法重名，因为 Java 命名规范不建议使用这样的标识符来为方法命名；第二、在原有方法前加上 &lt;code class=&quot;highlighter-rouge&quot;&gt;__rpc_&lt;/code&gt; 前缀，能够做到与原有方法对应，便于阅读和理解。示例如下：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;interface&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 保持原有方法不变&lt;/span&gt;
  &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 添加一个方法，新方法需要有返回值&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__rpc_create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后，在实现类中实现这个新方法：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nd&quot;&gt;@Service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userService&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;DefaultUserService&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;...&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 保持原有方法实现不变&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Date&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userRepository&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;LOGGER&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;info&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;toString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;c1&quot;&gt;// 添加新方法的实现&lt;/span&gt;
  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__rpc_create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 调用原来的方法&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// 返回变更后的数据&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;有一点需要展开解释：在 &lt;code class=&quot;highlighter-rouge&quot;&gt;__rpc_create&lt;/code&gt; 方法中，因为 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 参数是以引用的方式传递给 &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; 方法的，因此 &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; 方法对参数所做的修改是能够被 &lt;code class=&quot;highlighter-rouge&quot;&gt;__rpc_create&lt;/code&gt; 方法获取到的。这以后就与前面回传的逻辑是相同的了。&lt;/p&gt;

&lt;p&gt;第三，在服务引用端添加本地存根（有关本地存根的概念及用法，请参考&lt;a href=&quot;http://dubbo.io/books/dubbo-user-book/demos/local-stub.html&quot;&gt;官方文档&lt;/a&gt;）。&lt;/p&gt;

&lt;p&gt;需要在 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-service-reference&lt;/code&gt; 模块中添加一个类 &lt;code class=&quot;highlighter-rouge&quot;&gt;UserServiceStub&lt;/code&gt;，内容如下：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;UserServiceStub&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;UserServiceStub&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;UserService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getById&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;id&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;newUser&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;__rpc_create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;setCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;newUser&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getCreatedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;__rpc_create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;User&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;userService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;__rpc_create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;user&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;该类型即为本地存根。简单来说，就是在调用方调用本地代理的方法之前，会先去调用本地存根中相应的方法，因此本地存根与服务提供方和服务引用方需要实现同样的接口。本地存根中的构造函数是必须的，且方法签名也是被约定好的——需要传入本地代理作为参数。其中 &lt;code class=&quot;highlighter-rouge&quot;&gt;getById&lt;/code&gt; 和 &lt;code class=&quot;highlighter-rouge&quot;&gt;__rpc_create&lt;/code&gt; 方法都是直接调用了本地代理中的方法，不必过多关注，重点来说说 &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; 方法。首先，&lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; 调用了本地存根中的 &lt;code class=&quot;highlighter-rouge&quot;&gt;__rpc_create&lt;/code&gt; 方法，这个方法透过本地代理访问到了服务提供方的相应方法，并成功接收了返回值 &lt;code class=&quot;highlighter-rouge&quot;&gt;newUser&lt;/code&gt;，这个返回值是包含修改后的 &lt;code class=&quot;highlighter-rouge&quot;&gt;createdTime&lt;/code&gt; 字段的，于是我们要做的事情就是从 &lt;code class=&quot;highlighter-rouge&quot;&gt;newUser&lt;/code&gt; 对象里面获取到 &lt;code class=&quot;highlighter-rouge&quot;&gt;createdTime&lt;/code&gt; 字段的值，并设置给 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 参数，以达到产生副作用的效果。此时 &lt;code class=&quot;highlighter-rouge&quot;&gt;user&lt;/code&gt; 参数会带着新设置的 &lt;code class=&quot;highlighter-rouge&quot;&gt;createdTime&lt;/code&gt; 的值，将其“传递”给 &lt;code class=&quot;highlighter-rouge&quot;&gt;create&lt;/code&gt; 方法的调用方。&lt;/p&gt;

&lt;p&gt;最后，在 &lt;code class=&quot;highlighter-rouge&quot;&gt;dubbo-references.xml&lt;/code&gt; 文件中修改一处配置，以启用该本地存根：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 最后一行声明了使用该本地存根 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dubbo:reference&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;userServiceReference&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;interface=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;net.tangrui.demo.dubbo.hello.service.UserService&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;version=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;1.0&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;stub=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;net.tangrui.demo.dubbo.hello.service.stub.UserServiceStub&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;鉴于本地存根的工作机制，我们是不需要修改调用方 &lt;code class=&quot;highlighter-rouge&quot;&gt;hello-web&lt;/code&gt; 模块中的任何代码及配置的。编译、运行并测试，同样可以达到我们想要的效果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2017/dubbo/create-user-using-stub.png&quot; alt=&quot;第二种实现方式&quot; /&gt;&lt;/p&gt;

&lt;p&gt;这种实现方式会比第一种方式改进不少，但也有致命弱点：&lt;/p&gt;

&lt;ol&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;/ol&gt;

&lt;h1 id=&quot;五总结&quot;&gt;五、总结&lt;/h1&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>
        <pubDate>Mon, 21 Nov 2016 02:51:00 +0000</pubDate>
        <link>http://www.tangrui.net/2016/remodeling-a-legacy-monolithic-system-to-microservices-using-dubbo.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2016/remodeling-a-legacy-monolithic-system-to-microservices-using-dubbo.html</guid>
        
        <category>微服务</category>
        
        <category>Dubbo</category>
        
        <category>Spring Boot</category>
        
        <category>Spring Cloud</category>
        
        <category>RPC</category>
        
        <category>RESTful</category>
        
        
        <category>技术</category>
        
      </item>
    
      <item>
        <title>Zipkin 和 Pinpoint 选型对比</title>
        <description>&lt;h1 id=&quot;一背景&quot;&gt;一、背景&lt;/h1&gt;

&lt;p&gt;Pinpoint 是一款开源的应用程序性能管理（Application Performance Management）工具，开发团队来自韩国的搜索引擎门户 Naver（截止到 2016 年 5月，Alexa 全球排名第 58 位，韩国本土排名第一位）。该项目于 2012 年 7 月开始，2015 年 1 月开源，至今的稳定版本是 1.5.2。与 Zipkin 类似，其理论基础也是基于 Google Dapper 的那篇论文。&lt;/p&gt;

&lt;h1 id=&quot;二pinpoint-与-zipkin-的差异性&quot;&gt;二、Pinpoint 与 Zipkin 的差异性&lt;/h1&gt;

&lt;p&gt;Pinpoint 与 Zipkin 有明显的差异，主要体现在如下几个方面：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;Pinpoint 是一个完整的性能监控解决方案：有从探针、收集器、存储到 Web 界面等全套体系；而 Zipkin 只侧重收集器和存储服务，虽然也有用户界面，但其功能与 Pinpoint 不可同日而语。反而 Zipkin 提供有 Query 接口，更强大的用户界面和系统集成能力，可以基于该接口二次开发实现。&lt;/li&gt;
  &lt;li&gt;Zipkin 官方提供有基于 Finagle 框架（Scala 语言）的接口，而其他框架的接口由社区贡献，目前可以支持 Java、Scala、Node、Go、Python、Ruby 和 C# 等主流开发语言和框架；但是 Pinpoint 目前只有官方提供的 Java Agent 探针，其他的都在请求社区支援中（请参见 &lt;a href=&quot;https://github.com/naver/pinpoint/issues/1759&quot;&gt;#1759&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/naver/pinpoint/issues/1760&quot;&gt;#1760&lt;/a&gt;）。&lt;/li&gt;
  &lt;li&gt;Pinpoint 提供有 Java Agent 探针，通过字节码注入的方式实现调用拦截和数据收集，可以做到真正的代码无侵入，只需要在启动服务器的时候添加一些参数，就可以完成探针的部署；而 Zipkin 的 Java 接口实现 Brave，只提供了基本的操作 API，如果需要与框架或者项目集成的话，就需要手动添加配置文件或增加代码。&lt;/li&gt;
  &lt;li&gt;Pinpoint 的后端存储基于 HBase，而 Zipkin 基于 Cassandra。&lt;/li&gt;
&lt;/ol&gt;

&lt;h1 id=&quot;三pinpoint-与-zipkin-的相似性&quot;&gt;三、Pinpoint 与 Zipkin 的相似性&lt;/h1&gt;

&lt;p&gt;Pinpoint 与 Zipkin 都是基于 Google Dapper 的那篇论文，因此理论基础大致相同。两者都是将服务调用拆分成若干有级联关系的 Span，通过 SpanId 和 ParentSpanId 来进行调用关系的级联；最后再将整个调用链流经的所有的 Span 汇聚成一个 Trace，报告给服务端的 collector 进行收集和存储。&lt;/p&gt;

&lt;p&gt;即便在这一点上，Pinpoint 所采用的概念也不完全与那篇论文一致。比如他采用 TransactionId 来取代 TraceId，而真正的 TraceId 是一个结构，里面包含了 TransactionId, SpanId 和 ParentSpanId。而且 Pinpoint 在 Span 下面又增加了一个 SpanEvent 结构，用来记录一个 Span 内部的调用细节（比如具体的方法调用等等），因此 Pinpoint 默认会比 Zipkin 记录更多的跟踪数据。但是理论上并没有限定 Span 的粒度大小，所以一个服务调用可以是一个 Span，那么每个服务中的方法调用也可以是个 Span，这样的话，其实 Brave 也可以跟踪到方法调用级别，只是具体实现并没有这样做而已。&lt;/p&gt;

&lt;h1 id=&quot;四比较&quot;&gt;四、比较&lt;/h1&gt;

&lt;p&gt;下文主要通过对比，列出两者的优缺点，以提供选型参考。&lt;/p&gt;

&lt;h2 id=&quot;41字节码注入-vs-api-调用&quot;&gt;4.1、字节码注入 vs API 调用&lt;/h2&gt;

&lt;p&gt;根据上文所述，这一点是两者最大的差异。Pinpoint 实现了基于字节码注入的 Java Agent 探针，而 Zipkin 的 Brave 框架仅仅提供了应用层面的 API，但是细想问题远不那么简单。字节码注入是一种简单粗暴的解决方案，理论上来说无论任何方法调用，都可以通过注入代码的方式实现拦截，也就是说没有实现不了的，只有不会实现的。但 Brave 则不同，其提供的应用层面的 API 还需要框架底层驱动的支持，才能实现拦截。比如，MySQL 的 JDBC 驱动，就提供有注入 interceptor 的方法，因此只需要实现 StatementInterceptor 接口，并在 Connection String 中进行配置，就可以很简单的实现相关拦截；而与此相对的，低版本的 MongoDB 的驱动或者是 Spring Data MongoDB 的实现就没有如此接口，想要实现拦截查询语句的功能，就比较困难。&lt;/p&gt;

&lt;p&gt;因此在这一点上，Brave 是硬伤，无论使用字节码注入多么困难，但至少也是可以实现的，但是 Brave 却有无从下手的可能，而且是否可以注入，能够多大程度上注入，更多的取决于框架的 API 而不是自身的能力。（此问题在下一节继续讨论另外一种可能性）&lt;/p&gt;

&lt;h2 id=&quot;42难度及成本&quot;&gt;4.2、难度及成本&lt;/h2&gt;

&lt;p&gt;经过简单阅读 Pinpoint 和 Brave 插件的代码，可以发现两者的实现难度有天壤之别。在都没有任何开发文档支撑的前提下，Brave 比 Pinpoint 更容易上手。Brave 的代码量很少，核心功能都集中在 brave-core 这个模块下，一个中等水平的开发人员，可以在一天之内读懂其内容，并且能对 API 的结构有非常清晰的认识。&lt;/p&gt;

&lt;p&gt;Pinpoint 的代码封装也是非常好的，尤其是针对字节码注入的上层 API 的封装非常出色，但是这依然要求阅读人员对字节码注入多少有一些了解，虽然其用于注入代码的核心 API 并不多，但要想了解透彻，恐怕还得深入 Agent 的相关代码，比如很难一目了然的理解 addInterceptor 和 addScopedInterceptor 的区别，而这两个方法就是位于 Agent 的有关类型中。&lt;/p&gt;

&lt;p&gt;因为 Brave 的注入需要依赖底层框架提供相关接口，因此并不需要对框架有一个全面的了解，只需要知道能在什么地方注入，能够在注入的时候取得什么数据就可以了。就像上面的例子，我们根本不需要知道 MySQL 的 JDBC Driver 是如何实现的也可以做到拦截 SQL 的能力。但是 Pinpoint 就不然，因为 Pinpoint 几乎可以在任何地方注入任何代码，这需要开发人员对所需注入的库的代码实现有非常深入的了解，通过查看其 MySQL 和 Http Client 插件的实现就可以洞察这一点，当然这也从另外一个层面说明 Pinpoint 的能力确实可以非常强大，而且其默认实现的很多插件已经做到了非常细粒度的拦截。&lt;/p&gt;

&lt;p&gt;针对底层框架没有公开 API 的时候，其实 Brave 也并不完全无计可施，我们可以采取 AOP 的方式，一样能够将相关拦截注入到指定的代码中，而且显然 AOP 的应用要比字节码注入简单很多。&lt;/p&gt;

&lt;p&gt;以上这些直接关系到实现一个监控的成本，在 Pinpoint 的官方技术文档中，给出了一个参考数据。如果对一个系统集成的话，那么用于开发 Pinpoint 插件的成本是 100，将此插件集成入系统的成本是 0；但对于 Brave，插件开发的成本只有 20，而集成成本是 10。从这一点上可以看出官方给出的成本参考数据是 5:1。但是官方又强调了，如果有 10 个系统需要集成的话，那么总成本就是 10 * 10 + 20 = 120，就超出了 Pinpoint 的开发成本 100，而且需要集成的服务越多，这个差距就越大。&lt;/p&gt;

&lt;h2 id=&quot;43通用性和扩展性&quot;&gt;4.3、通用性和扩展性&lt;/h2&gt;

&lt;p&gt;很显然，这一点上 Pinpoint 完全处于劣势，从社区所开发出来的集成接口就可见一斑。&lt;/p&gt;

&lt;p&gt;Pinpoint 的数据接口缺乏文档，而且也不太标准（参考论坛讨论帖），需要阅读很多代码才可能实现一个自己的探针（比如 Node 的或者 PHP 的）。而且团队为了性能考虑使用了 Thrift 作为数据传输协议标准，比起 HTTP 和 JSON 而言难度增加了不少。&lt;/p&gt;

&lt;h2 id=&quot;44社区支持&quot;&gt;4.4、社区支持&lt;/h2&gt;

&lt;p&gt;这一点也不必多说，Zipkin 由 Twitter 开发，可以算得上是明星团队，而 Naver 的团队只是一个默默无闻的小团队（从 &lt;a href=&quot;https://github.com/naver/pinpoint/issues/1759&quot;&gt;#1759&lt;/a&gt; 的讨论中可以看出）。虽然说这个项目在短期内不太可能消失或停止更新，但毕竟不如前者用起来更加放心。而且没有更多社区开发出来的插件，让 Pinpoint 只依靠团队自身的力量完成诸多框架的集成实属困难，而且他们目前的工作重点依然是在提升性能和稳定性上。&lt;/p&gt;

&lt;h2 id=&quot;45其他&quot;&gt;4.5、其他&lt;/h2&gt;

&lt;p&gt;Pinpoint 在实现之初就考虑到了性能问题，www.naver.com 网站的后端某些服务每天要处理超过 200 亿次的请求，因此他们会选择 Thrift 的二进制变长编码格式、而且使用 UDP 作为传输链路，同时在传递常量的时候也尽量使用数据参考字典，传递一个数字而不是直接传递字符串等等。这些优化也增加了系统的复杂度：包括使用 Thrift 接口的难度、UDP 数据传输的问题、以及数据常量字典的注册问题等等。&lt;/p&gt;

&lt;p&gt;相比之下，Zipkin 使用熟悉的 Restful 接口加 JSON，几乎没有任何学习成本和集成难度，只要知道数据传输结构，就可以轻易的为一个新的框架开发出相应的接口。&lt;/p&gt;

&lt;p&gt;另外 Pinpoint 缺乏针对请求的采样能力，显然在大流量的生产环境下，不太可能将所有的请求全部记录，这就要求对请求进行采样，以决定什么样的请求是我需要记录的。Pinpoint 和 Brave 都支持采样百分比，也就是百分之多少的请求会被记录下来。但是，除此之外 Brave 还提供了 Sampler 接口，可以自定义采样策略，尤其是当进行 A/B 测试的时候，这样的功能就非常有意义了。&lt;/p&gt;

&lt;h1 id=&quot;五总结&quot;&gt;五、总结&lt;/h1&gt;

&lt;p&gt;从短期目标来看，Pinpoint 确实具有压倒性的优势：无需对项目代码进行任何改动就可以部署探针、追踪数据细粒化到方法调用级别、功能强大的用户界面以及几乎比较全面的 Java 框架支持。但是长远来看，学习 Pinpoint 的开发接口，以及未来为不同的框架实现接口的成本都还是个未知数。相反，掌握 Brave 就相对容易，而且 Zipkin 的社区更加强大，更有可能在未来开发出更多的接口。在最坏的情况下，我们也可以自己通过 AOP 的方式添加适合于我们自己的监控代码，而并不需要引入太多的新技术和新概念。而且在未来业务发生变化的时候，Pinpoint 官方提供的报表是否能满足要求也不好说，增加新的报表也会带来不可以预测的工作难度和工作量。&lt;/p&gt;
</description>
        <pubDate>Tue, 24 May 2016 05:33:00 +0000</pubDate>
        <link>http://www.tangrui.net/2016/zipkin-vs-pinpoint.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2016/zipkin-vs-pinpoint.html</guid>
        
        <category>架构</category>
        
        <category>Zipkin</category>
        
        <category>Pinpoint</category>
        
        <category>Dapper</category>
        
        <category>APM</category>
        
        <category>微服务</category>
        
        <category>服务监控</category>
        
        
        <category>技术</category>
        
      </item>
    
      <item>
        <title>使用 Zipkin 和 Brave 实现分布式系统追踪（基础篇）
</title>
        <description>&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/zipkin.jpg&quot; alt=&quot;Zipkin&quot; /&gt;&lt;/p&gt;

&lt;h1 id=&quot;一zipkin&quot;&gt;一、Zipkin&lt;/h1&gt;

&lt;h2 id=&quot;11简介&quot;&gt;1.1、简介&lt;/h2&gt;

&lt;p&gt;&lt;a href=&quot;http://zipkin.io&quot;&gt;Zipkin&lt;/a&gt; 是一款开源的分布式实时数据追踪系统 (Distributed Tracking System)，基于 &lt;a href=&quot;http://research.google.com/pubs/pub36356.html&quot;&gt;Google Dapper&lt;/a&gt; 的论文设计而来，由 Twitter 公司开发贡献。其主要功能是聚集来自各个异构系统的实时监控数据，用来追踪微服务架构下的系统延时问题。&lt;/p&gt;

&lt;p&gt;应用系统需要进行装备 (instrument) 以向 Zipkin 报告数据。Zipkin 的用户界面可以呈现一幅关联图表，以显示有多少被追踪的请求通过了每一层应用。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/zipkin-waterfall.png&quot; alt=&quot;瀑布图&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Zipkin 以 Trace 结构表示对一次请求的追踪，又把每个 Trace 拆分为若干个有依赖关系的 Span。在微服务架构中，一次用户请求可能会由后台若干个服务负责处理，那么每个处理请求的服务就可以理解为一个 Span (可以包括 API 服务，缓存服务，数据库服务以及报表服务等)。当然这个服务也可能继续请求其他的服务，因此 Span 是一个树形结构，以体现服务之间的调用关系。&lt;/p&gt;

&lt;p&gt;Zipkin 的用户界面除了可以查看 Span 的依赖关系之外，还以瀑布图的形式显示了每个 Span 的耗时情况，可以一目了然的看到各个服务的性能状况。打开每个 Span，还有更详细的以键值对形式呈现的数据，而且这些数据可以在装备应用的时候自行添加。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/zipkin-traces-1.png&quot; alt=&quot;Span 详细信息&quot; /&gt;&lt;/p&gt;

&lt;p&gt;从图中可以读出如下的调用关系：整个调用链中有两个微服务 service1 和 service2，在 10ms (相对时间点) 的时候，service1 作为客户端向 service2 发送了一个请求 (Client Send)，之后 service2 服务于 19ms 的时候收到请求 (Server Receive)，并用了 12ms 的时间来处理，并于 31ms 时刻将数据返回 (Server Send)，最后 service1 服务于 1ms 以后接收到此数据 (Client Receive)，因此整个过程共耗时 22ms。图中还给出了 service1 访问 service2 服务前后 Http Client 连接池的状态信息。&lt;/p&gt;

&lt;h2 id=&quot;12架构&quot;&gt;1.2、架构&lt;/h2&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/zipkin-architecture-0.png&quot; alt=&quot;Zipkin 架构&quot; /&gt;&lt;/p&gt;

&lt;p&gt;如图所示，Zipkin 主要由四部分构成：收集器、数据存储、查询以及 Web 界面。Zipkin 的收集器负责将各系统报告过来的追踪数据进行接收；而数据存储默认使用 Cassandra，也可以替换为 MySQL；查询服务用来向其他服务提供数据查询的能力，而 Web 服务是官方默认提供的一个图形用户界面。&lt;/p&gt;

&lt;p&gt;而各个异构的系统服务向 Zipkin 报告数据的架构如下图。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/zipkin-architecture-1.png&quot; alt=&quot;服务向 Zipkin 报告数据&quot; /&gt;&lt;/p&gt;

&lt;h2 id=&quot;13运行&quot;&gt;1.3、运行&lt;/h2&gt;

&lt;p&gt;使用 Docker 运行 Zipkin 最为简单，其过程如下：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git clone https://github.com/openzipkin/docker-zipkin
&lt;span class=&quot;nb&quot;&gt;cd &lt;/span&gt;docker-zipkin
docker-compose up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;这样启动，默认会使用 Cassandra 数据库，如果想改用 MySQL，可以换做以下命令启动：&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# 用 MySQL 运行 Zipkin&lt;/span&gt;
docker-compose &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker-compose.yml &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; docker-compose-mysql.yml up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;启动成功以后，可以通过 &lt;code class=&quot;highlighter-rouge&quot;&gt;http://&amp;lt;ip-address&amp;gt;:8080&lt;/code&gt; 来访问。具体获取 IP 地址的方法请参阅 Docker 的相关文档。&lt;/p&gt;

&lt;h1 id=&quot;二brave&quot;&gt;二、Brave&lt;/h1&gt;

&lt;h2 id=&quot;21简介&quot;&gt;2.1、简介&lt;/h2&gt;

&lt;p&gt;Brave 是用来装备 Java 程序的类库，提供了面向标准 Servlet、Spring MVC、Http Client、JAX RS、Jersey、Resteasy 和 MySQL 等接口的装备能力，可以通过编写简单的配置和代码，让基于这些框架构建的应用可以向 Zipkin 报告数据。同时 Brave 也提供了非常简单且标准化的接口，在以上封装无法满足要求的时候可以方定制便与扩展。&lt;/p&gt;

&lt;h2 id=&quot;22初始化&quot;&gt;2.2、初始化&lt;/h2&gt;

&lt;p&gt;Brave 的初始化就是要构建 Brave 类的实例，该库提供了 Builder 类用来完成这件事情。&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;注：下文中约定，大写的 &lt;em&gt;Brave&lt;/em&gt; 指该 Java 类库，而 &lt;em&gt;Brave 类&lt;/em&gt;指 com.github.kristofa.brave.Brave 类型，而小写的 &lt;em&gt;brave&lt;/em&gt; 指该类型的实例。&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 初始化 Brave&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brave&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中的 serviceName 是当前服务的名称，这个名称会出现在所有跟该服务有关的 Span 中。默认情况下，Brave 不会将收集到的监控数据发送给 Zipkin 服务器，而是会以日志的形式打印到控制台。如果需要将数据发送给服务器，就需要引入 HttpSpanCollector 类。当前版本 (3.8.0) 将这个类命名为 Collector，这个概念容易跟 Zipkin 自身的 Collector 相混淆，因此在 &lt;a href=&quot;https://github.com/openzipkin/brave/issues/173&quot;&gt;Issue&lt;/a&gt; 中官方建议将其更名为 Reporter，也就是说这个类是用来向 Zipkin 的 Collector 报告数据的。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/zipkin-reporter.png&quot; alt=&quot;Zipkin Reporter&quot; /&gt;&lt;/p&gt;

&lt;p&gt;使用 HttpSpanCollector 的方法如下：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// 使用 HttpSpanCollector 初始化 Brave&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;spanCollector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HttpSpanCollector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
    &lt;span class=&quot;s&quot;&gt;&quot;http://localhost:9411&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;EmptySpanCollectorMetricsHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;brave&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;使用 HttpSpanCollector.create 方法可以创建该类的一个实例，第一个参数就是 Zipkin 服务的地址 (默认部署时的端口为 9411)。&lt;/p&gt;

&lt;p&gt;如果使用 Spring 的话，为了方便扩展，建议添加一个名为 ZipkinBraveFactoryBean 的类，其内容大致如下：&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// ZipkinBraveFactoryBean&lt;/span&gt;
&lt;span class=&quot;kn&quot;&gt;package&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;net&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;tangrui&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;example&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
 
&lt;span class=&quot;c1&quot;&gt;// 注：省略所有的 import&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ZipkinBraveFactoryBean&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;implements&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;FactoryBean&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
 
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zipkinHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
 
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
 
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setServiceName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;serviceName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
 
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;setZipkinHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zipkinHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;zipkinHost&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;zipkinHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
 
  &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;createInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;serviceName&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;throw&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;BeanInitializationException&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;Property serviceName
must be set.&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
 
    &lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;Builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;serviceName&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;zipkinHost&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;!&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;equals&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;zipkinHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;spanCollector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;HttpSpanCollector&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;create&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;zipkinHost&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;EmptySpanCollectorMetricsHandler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;instance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;builder&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
 
  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;getObject&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;throws&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Exception&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;instance&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createInstance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;instance&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
 
  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;?&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;getObjectType&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Brave&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
 
  &lt;span class=&quot;nd&quot;&gt;@Override&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isSingleton&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;true&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
  &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后只需要在 application-context.xml 配置文件中使用该 FactoryBean 就可以了：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 使用 ZipkinBraveFactoryBean --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;brave&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;net.tangrui.example.brave.ZipkinBraveFactoryBean&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;p:serviceName=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;p:zipkinHost=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;http://localhost:9411&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;23装备标准的-servlet-应用&quot;&gt;2.3、装备标准的 Servlet 应用&lt;/h2&gt;

&lt;p&gt;Brave 提供了 brave-web-servlet-filter 模块，可以为标准的 Servlet 应用添加向 Zipkin 服务器报告数据的能力，需要做的就是在 web.xml 文件增加一个 BraveServletFilter。&lt;/p&gt;

&lt;p&gt;不过这个 Filter 在初始化的时候需要传入几个参数，这些参数可以通过 brave 对象的对应方法获得，但是注入这些构造参数，最简单的办法还是使用 Spring 提供的 DelegatingFilterProxy。&lt;/p&gt;

&lt;p&gt;在 web.xml 中添加如下内容 (最好配置为第一个 Filter，以便从请求最开始就记录数据)：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 配置 web.xml --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;filter&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;filter-name&amp;gt;&lt;/span&gt;braveFilter&lt;span class=&quot;err&quot;&gt;&amp;lt;&lt;/span&gt;&amp;gt;/filter-name&amp;gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;filter-class&amp;gt;&lt;/span&gt;
    org.springframework.web.filter.DelegatingFilterProxy
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter-class&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;init-param&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;param-name&amp;gt;&lt;/span&gt;targetFilterLifecycle&lt;span class=&quot;nt&quot;&gt;&amp;lt;/param-name&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;param-value&amp;gt;&lt;/span&gt;true&lt;span class=&quot;nt&quot;&gt;&amp;lt;/param-value&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/init-param&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;filter-mapping&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;filter-name&amp;gt;&lt;/span&gt;braveFilter&lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter-name&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;url-pattern&amp;gt;&lt;/span&gt;/*&lt;span class=&quot;nt&quot;&gt;&amp;lt;/url-pattern&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dispatcher&amp;gt;&lt;/span&gt;REQUEST&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dispatcher&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dispatcher&amp;gt;&lt;/span&gt;FORWARD&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dispatcher&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dispatcher&amp;gt;&lt;/span&gt;INCLUDE&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dispatcher&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;dispatcher&amp;gt;&lt;/span&gt;ERROR&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dispatcher&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/filter-mapping&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;然后在配置文件中添加以下内容 (创建 brave Bean 的有关代码请参考上文)：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!--
 - 配置 BraveServletFilter
 - 注意：这里的 id 要使用和 web.xml 中的 filter-name 同样的值
--&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;id=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;braveFilter&quot;&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.github.kristofa.brave.servlet.BraveServletFilter&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#{brave.serverRequestInterceptor()}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#{brave.serverResponseInterceptor()}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt;
      &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.github.kristofa.brave.http.DefaultSpanNameProvider&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;/constructor-arg&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/bean&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;最后一个类 com.github.kristofa.brave.http.DefaultSpanNameProvider 存在于 brave-http 模块中。当使用 Maven 或 Gradle 来管理项目的时候，brave-http 会随着 brave-web-servlet-filter 的引入被自动关联进来。&lt;/p&gt;

&lt;p&gt;一切无误的话就可以启动服务。如果给定了 zipkinHost 参数，数据就会被发送到指定的 Zipkin 服务器上，然后可以在其 Web 界面上看到相关内容；否则会有类似如下的信息打印到系统控制台 (做了格式美化)：&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;traceId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;27bf14862307cd99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;name&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;post&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;id&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;d79a683e2900c293&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;parentId&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;27bf14862307cd99&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.463737111294e+15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;duration&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;772000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;annotations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;endpoint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;service1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ipv4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.20.13.41&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.463737111294e+15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cs&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;endpoint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;service1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ipv4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.20.13.41&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;timestamp&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;1.463737112066e+15&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;cr&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;],&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;binaryAnnotations&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;route.conn_manager_stats.after&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[leased: 1; pending: 0; available: 0; max: 1000]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;endpoint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;service1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ipv4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.20.13.41&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;route.conn_manager_stats.before&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[leased: 0; pending: 0; available: 0; max: 1000]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;endpoint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;service1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ipv4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.20.13.41&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;total.conn_manager_stats.after&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[leased: 1; pending: 0; available: 0; max: 1000]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;endpoint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;service1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;    
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ipv4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.20.13.41&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;key&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;total.conn_manager_stats.before&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;value&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;[leased: 0; pending: 0; available: 0; max: 1000]&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;endpoint&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;serviceName&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;service1&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
        &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;ipv4&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;172.20.13.41&quot;&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
      &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;23装备-spring-mvc-应用&quot;&gt;2.3、装备 Spring MVC 应用&lt;/h2&gt;

&lt;p&gt;Brave 自带了 brave-spring-web-servlet-interceptor 模块，因此装备 Spring MVC 项目变得非常容易，只需要在配置文件中添加一些 interceptor 就好了：&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 配置 Spring MVC 拦截器 --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;mvc:interceptors&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.github.kristofa.brave.spring.ServletHandlerInterceptor&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#{brave.serverRequestInterceptor()}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#{brave.serverResponseInterceptor()}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&amp;gt;&lt;/span&gt;
      &lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.github.kristofa.brave.http.DefaultSpanNameProvider&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;/constructor-arg&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#{brave.serverSpanThreadBinder()}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/bean&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/mvc:interceptors&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;24装备-mysql-服务&quot;&gt;2.4、装备 MySQL 服务&lt;/h2&gt;

&lt;p&gt;brave-mysql 模块在 JDBC 驱动层面添加了一些拦截器，可以对 MySQL 的查询进行监控。在使用之前也需要通过 Spring 进行一下配置。&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;&amp;lt;!-- 注入 MySQLStatementInterceptorManagementBean --&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;bean&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;class=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;com.github.kristofa.brave.mysql.MySQLStatementInterceptorManagementBean&quot;&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;destroy-method=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;close&quot;&lt;/span&gt;&lt;span class=&quot;nt&quot;&gt;&amp;gt;&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;&amp;lt;constructor-arg&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;value=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;#{brave.clientTracer()}&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;/&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/bean&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;该配置的目的是要给 MySQLStatementInterceptorManagementBean 类注入一个 ClientTracer 实例，这个实例会在后来的 MySQL JDBC 驱动的拦截器中被使用。初始化完成以后只需要在连接字符串中添加如下参数就可以了：&lt;/p&gt;

&lt;div class=&quot;highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# 连接字符串
?statementInterceptors=com.github.kristofa.brave.mysql.MySQLStatementInterceptor&amp;amp;amp;zipkinServiceName=myDatabaseService
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;其中的 zipkinServiceName 用来指定该 MySQL 服务的名称，如果省略的话，会默认以 mysql-${databaseName} 的形式来呈现。&lt;/p&gt;

&lt;p&gt;这里需要特别说明一点，因为 MySQL 服务是跟 Java 服务分离的，因此上文初始化 brave 对象时提供的服务名称，并不适用于 MySQL 服务，所以才需要在这里另外指定。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/zipkin-traces-2.png&quot; alt=&quot;MySQL 服务的监控数据&quot; /&gt;&lt;/p&gt;

&lt;p&gt;可以看出，添加了 statement interceptor 之后，可以看到 service2 请求 MySQL 查询的起止时间，以及执行的 SQL 语句等信息。&lt;/p&gt;

&lt;h1 id=&quot;三总结&quot;&gt;三、总结&lt;/h1&gt;

&lt;p&gt;本文主要介绍了 Zipkin 服务和其 Java 库 Brave 的一些基本概念及原理，并且针对 Brave 开箱提供的一些装备组件进行了详细的使用说明。在后面进阶篇的文章中，会对如何扩展 Brave 以实现自定义监控信息的内容进行介绍，敬请期待！&lt;/p&gt;
</description>
        <pubDate>Sat, 21 May 2016 14:25:53 +0000</pubDate>
        <link>http://www.tangrui.net/2016/implement-distributed-tracking-system-with-zipkin-and-brave.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2016/implement-distributed-tracking-system-with-zipkin-and-brave.html</guid>
        
        <category>架构</category>
        
        <category>Zipkin</category>
        
        <category>Brave</category>
        
        <category>Dapper</category>
        
        <category>APM</category>
        
        <category>微服务</category>
        
        <category>服务监控</category>
        
        
        <category>技术</category>
        
      </item>
    
      <item>
        <title>企业级 IM 的差异化发展之路</title>
        <description>&lt;p&gt;&lt;img src=&quot;/static/uploads/2016/dingding-vs-wechat.jpg&quot; alt=&quot;钉钉 vs 微信&quot; /&gt;&lt;/p&gt;

&lt;p&gt;2016 年 4 月 18 日，微信团队发布了继微信和微信支付后的第三个重量级产品企业微信，正式吹响了与阿里钉钉交锋的号角，并且还扬言：要像干掉来往一样的干掉钉钉。一时间，企业级 IM 这个既陌生又熟悉的产品，成为了互联网界热议的话题。&lt;/p&gt;

&lt;p&gt;那么企业级 IM 到底是什么？它又跟移动办公产品有什么区别？当钉钉和企业微信展开厮杀的时候，企业级 IM 的差异化发展之路到底在何方？&lt;/p&gt;

&lt;p&gt;既然是叫“企业级 IM”，那么不难看出其产品特性的两个着眼点：企业应用和即时通讯。企业应用一方面是应用于企业内部，另一方面也在于能够整合企业内部的各种管理流程与制度。小了来说是各种轻量级应用，大了来说可以汇聚成管理信息系统。而即时通讯就更不言而喻了，曾经当 QQ 几乎成为国内即时通讯代名词的时候，有多少企业早已不由自主的通过 QQ 群完成了许多内部的沟通事宜，可见企业还是非常需要这样一类产品的。无奈的是，无论 QQ 还是微信，其社交成分要远远大于协同办公的成分，无法成为组织内部沟通协作的首选。&lt;/p&gt;

&lt;p&gt;当来往完败给微信的时候，阿里仍不死心的想要切入即时通讯这个腾讯最核心的业务领域，于是就此诞生了钉钉。马云更是豪赌 10 亿，另辟蹊径的从企业内用户寻找突破口，让钉钉迅速站稳了脚跟。腹地被偷袭的腾讯自然不会善罢甘休，企业级 IM 市场顿时风生水起、剑拔弩张。企业级 IM 毕竟不是什么新鲜玩意，国外的 Slack 和 HipChat 早已是行业中的佼佼者，但显然钉钉与企业微信跟 Slack 和 HipChat 看上去并不怎么相似，不只是 Copy to China 这么简单。其两者都在通讯功能的基础上增加了类似签到、审批、任务管理和文件共享等企业内常用的功能，而且还能够开放给第三方合作伙伴进行二次开发，俨然一个企业管理流程互联网化的开放平台，让很多中小企业似乎找到了协同办公的感觉，从而忘记了这些东西原本在移动办公领域已经是必备的功能了。&lt;/p&gt;

&lt;p&gt;然而企业内部的系统，必然不同于互联网化的系统。一个组织并不会因为“系出名门”、“免费”或“更加易用”等特性就放弃正在使用的古老系统；而且企业内部的管理流程也远非轻量级应用所能覆盖，尤其是在以流程驱动管理的企业内部，动辄十几步甚至几十步的流程都大有所在，而且流程信息量也绝非任务管理这等规模的应用所能比拟的；最后无论钉钉抑或企业微信，都还仅仅是将即时通讯和轻量级应用做了一个简单的叠加，流程数据还存在流程中，而碎片化的聊天信息依旧隐含着大量的珍贵知识，企业级 IM 是否应该走得更深入一些？&lt;/p&gt;

&lt;p&gt;文章 &lt;a href=&quot;http://firstround.com/review/An-Inside-Look-at-a-Flat-Organization-That-Serves-Millions&quot;&gt;An Inside Look at a Flat Organization That Serves Millions&lt;/a&gt; 的作者 Nate Lee 在介绍如何管理一个扁平化组织的时候，提出了一个命题： Assume that new hires won’t be able to talk to any of their co-workers for 48 hours. This is of course never the case, but if it was, they should have enough information at their fingertips to do their job. They should be able to read up on any problem or feature or question they have.（假设新来的员工在 48 小时内不能和其他同事讲话。在这种假定下，他们应该动动手指就能得到足够的信息去开展工作。如果在工作中遇到了问题，他们也应该能够通过阅读自己弄明白。）结合 Nate Lee 在文章中给出的解决方案，外加我个人的一些理解，我觉得企业级 IM 应该可以往如下一些方向发展。&lt;/p&gt;

&lt;h3 id=&quot;一流程与消息的整合&quot;&gt;一、流程与消息的整合&lt;/h3&gt;

&lt;p&gt;与其在企业级 IM 内部实现轻量级应用，不如让其与现有系统做更好的集成。在这一点上不能不提 HipChat，她与 Bitbucket、Jira 以及 Github 都有非常完美的集成，能够在上述系统发生变化的时候以即时消息的形式推送给用户，而且开放的 API 也使得越来越多的第三方系统能够与其整合，使其功能日益强大。如果真的要在企业级 IM 内部嵌入流程应用，那么就该想想如何实现融合。&lt;/p&gt;

&lt;p&gt;想象一个场景，开发人员在写程序的时候通常都要关注变更管理系统，而当一个问题并没有被很好描述的时候，开发人员要么像回复论坛中的帖子一样提出问题，并等待答复（就像 Github 那样），要么就更直接拉几个相关的人进入一个讨论组开始聊天，直到把问题讨论清楚。这两种方式显然都不是最优的。第一种方式就好像在使用邮件，邮件虽然更正式、更严谨，但是受到冷落的根本原因就是时效性，不能够“即时通讯”，只能书写然后等待。但是无疑所有的交流痕迹都会被保存下来，当日后需要回顾的时候，查阅就变得异常方便了；第二种方式解决了时效性的问题，但最严重的缺陷就是有可能丢失所有针对该问题的讨论细节，仅仅会把一些结论性质的东西更新到问题的描述中，更有可能连这些结论性质的东西都会随着懒惰与监管的不到位逐渐流失。&lt;/p&gt;

&lt;p&gt;或许想象中的系统应该是这样工作的。当开发人员需要针对某个问题展开讨论的时候，可以直接针对这个问题发起一个话题，工具应该有能力自动将相关人员拉入这个话题讨论组，并定位到当前的流程状态。所有接下来的讨论均会被这个话题记录下来，并关联到当前状态上。某些核心的讨论结果还可以直接更新回或添加到问题的描述信息，一次交流就解决了历史留存和沟通时效的问题，将来再回过头来查看的时候，每个状态的每个时间点上都有若干聊天记录呈现在旁边，辅佐查看问题的人了解达成最终结论的演变过程。&lt;/p&gt;

&lt;p&gt;不仅如此，即时通讯除了可以解决沟通上的时效性以外，结合流程的时候还能够提高流程的工作效率。就拿最简单的报销流程来讲，倘若一个报销事宜经由申请人填报、主管经理审批、部门总监审批、财务人员审批最后到公司负责人审批这样的流程。若其中任何一个审批环节出现问题，都会导致流程被退回。虽然这本身正是流程存在的意义，但是显然被退回的流程一定程度上影响了流程的运作效率。如果填报人员或某一级审批人员在处理信息的过程中发现有疑问，那么针对当前环节上的流程进行一番讨论，消除分歧，可以促使流程走到终点的可能性大大提高。&lt;/p&gt;

&lt;h3 id=&quot;二话题&quot;&gt;二、话题&lt;/h3&gt;

&lt;p&gt;其实上面的能力都离不开一个功能就是话题，可以有针对流程的话题，也可以有自由创建的话题。话题无非限定了群组里面讨论的内容，当一个话题讨论完成以后，就应该将其关闭，避免发生偏离。在需要的时候还可以将话题之间进行关联或者是让话题与流程、文档抑或某个用户进行关联。话题将碎片化的消息进行了归类与划分，多维度的话题关联让话题有了更多的延展，同时还能加快话题的定位速度，久而久之一个由话题构建成的知识库就慢慢诞生了，无需再特意去整理这些交流过程中显现出来的隐性知识，后来者可以非常方便的经由话题逐渐深入了解组织正在发生的事情。&lt;/p&gt;

&lt;h3 id=&quot;三个人助理&quot;&gt;三、个人助理&lt;/h3&gt;

&lt;p&gt;个人助理的想法是由微软小冰带来的灵感，她就像是一个逗逼的邻桌，只要你跟她说话，她就总能出乎意料的给你反馈。既然企业级 IM 定位在了聊天，那么为什么我还要去应用中开始某个八竿子打不着的流程能？如果每个人都有一个像微软小冰那样的助手，你可以跟她说需要走一个流程、需要记录一份文档、需要展开一个话题甚至需要进行一次电话会议，她应该帮你把余下的事情都处理妥当，你需要的就等待最后的结果。Tony Aube 在 &lt;a href=&quot;http://techcrunch.com/2015/11/11/no-ui-is-the-new-ui&quot;&gt;No UI Is The New UI&lt;/a&gt; 中对未来的人机交互给出了大胆的假设，想必以后需要做什么事情之前都最好找你的机器人助手帮个忙。而且时间久了她还会更了解你，你无需再跟她说复杂的指令来让他做一个事情，可能仅仅一个字，甚至一个动作她就知道了你的初衷。&lt;/p&gt;

&lt;p&gt;还是以报销作为容易理解的场景，你直接跟你的助手说你要发起一个报销，她会告诉你报销的注意事项，以及最近是否有制度上的更新。然后她会根据权限和组织关系把此次报销的相关人员都拉入一个话题，你直接说出你要报销的条目，她会帮你将其整理成一份标准格式的表单，并提交到流程系统，同时知会相关人员。如果需要讨论就地就可以进行，而且信息会随着讨论的进行逐渐更新，直到最后你确认提交此次申请，其他人可以同时就完成审批。&lt;/p&gt;

&lt;h3 id=&quot;四状态更新&quot;&gt;四、状态更新&lt;/h3&gt;

&lt;p&gt;我们平时都喜欢更新 QQ 或微信的状态以及签名档，好让朋友们知道我们现在的心情或者正在做的一些事情，这个功能拿到工作中也同样适用。哪个领导不想知道自己的下属在做什么，难道要每天去问问看吗？当你在处理某项工作、打开某个文档抑或参与某个话题的时候，所有的状态都会自动进行更新，主管领导就可以看到每个人的当前工作内容，不会有人再不停的追问你在做什么，你的工作上的一切（包括发呆）都会被清楚的记录下来（可能会有种被窥视的感觉，不过汇报工作内容本来就是需要的，把控好力程度就 OK 了）。&lt;/p&gt;

&lt;p&gt;最后，所有这些设想中的功能都需要即使通讯与企业应用做到有机的结合，这本来也该是企业级 IM 天生具有的能力。这些异想天开的功能，嘴上说说容易，但实现起来还需要一段漫长的路，甚至是颠覆用户体验的变革。企业级 IM 产品必然会迎来一次集中爆发，让我们拭目以待。&lt;/p&gt;
</description>
        <pubDate>Sun, 08 May 2016 11:32:51 +0000</pubDate>
        <link>http://www.tangrui.net/2016/enterprise-im-differentiated-development.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2016/enterprise-im-differentiated-development.html</guid>
        
        <category>钉钉</category>
        
        <category>微信</category>
        
        <category>企业微信</category>
        
        <category>即时通讯</category>
        
        <category>IM</category>
        
        <category>移动办公</category>
        
        
        <category>产品</category>
        
      </item>
    
      <item>
        <title>Java 栈上的 JavaScript</title>
        <description>&lt;iframe width=&quot;560&quot; height=&quot;315&quot; src=&quot;https://www.youtube.com/embed/S0imw6Ycc64&quot; frameborder=&quot;0&quot; allowfullscreen=&quot;&quot;&gt;&lt;/iframe&gt;

&lt;p&gt;&lt;em&gt;&lt;small&gt;注：该视频托管在 Youtube 上面，你懂得该怎么做。&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;这是笔者在 &lt;a href=&quot;/2015/shenjs-2015&quot;&gt;ShenJS 2015&lt;/a&gt; 上面所做的一个 Lighting Talk，当时的题目叫做 Enterprise JavaScript。其实那天准备了很多功能演示和代码片段，无奈时间太短，只把幻灯片过了一遍就没有时间了，于是在这里做一个完整的介绍。&lt;/p&gt;

&lt;p&gt;Enterprise JavaScript 是一个非常庞大的话题，会涉及到 JavaScript 跟各种 Java 中间件的互操作问题，这里仅就如何在一个完整的 Java 栈上以 JavaScript 作为首要编程语言来提高 Java 系统的开发效率展开讨论。&lt;/p&gt;

&lt;p&gt;作为一门常年把持 &lt;a href=&quot;https://www.tiobe.com/tiobe-index&quot;&gt;TIOBE&lt;/a&gt; 排行榜冠军的静态编译类语言，Java 固然有数不胜数的优点，以至于其在企业级应用开发领域拥有不可替代的地位。但是，相信每一个 Java 开发人员都会在日常工作中忍受其种种弊端带来的烦恼，其中最令人无法忍受的正是其静态编译类的特性。静态类型在减少开发人员出错可能性的同时降低了语言的动态扩展能力；而编译执行除了能保证代码的编译期校验以外，更多的就只剩下无尽的等待了。尽管现在大多数的工具链和集成开发环境都可以做到增量编译，但是增量编译后可执行代码的热部署能力却是差强人意。尽管有一些&lt;a href=&quot;https://zeroturnaround.com/software/jrebel&quot;&gt;商业的&lt;/a&gt;或&lt;a href=&quot;http://www.hotswapagent.org&quot;&gt;开源的&lt;/a&gt;解决方案，但总归不是天生特性，使用起来很不方便。&lt;/p&gt;

&lt;p&gt;其实针对 Java 语言缺陷的改良，社区始终也没有停止尝试。著名的成果包括 &lt;a href=&quot;http://www.scala-lang.org&quot;&gt;Scala&lt;/a&gt;、&lt;a href=&quot;http://www.groovy-lang.org&quot;&gt;Groovy&lt;/a&gt;、&lt;a href=&quot;http://www.jython.org&quot;&gt;Jython&lt;/a&gt;、&lt;a href=&quot;http://jruby.org&quot;&gt;JRuby&lt;/a&gt; 以及我们今天要讨论的 JavaScript。社区通常都有这么一个共识：Java 虚拟机是个好东西，但 Java 语言本身不一定。因此改良的重点全部集中在语言层面，通过引入具有不同特性的新语言，经过编译器或解析器翻译成 Java 字节码，再交给虚拟机来运行。&lt;/p&gt;

&lt;p&gt;另一方面，JavaScript 近两年人气爆棚，有逐渐发展成为全栈开发语言的趋势。尽管在 Node 平台上使用 JavaScript 开发后端应用早已是习以为常的事情，但是在 Java 平台上，这件事情就不是那么显而易见了。说到在 Java 开发栈上运行 JavaScript 就不能不提大名鼎鼎的 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino&quot;&gt;Rhino&lt;/a&gt;。这是早在 &lt;a href=&quot;https://developer.mozilla.org/en-US/docs/Mozilla/Projects/Rhino/History&quot;&gt;1997&lt;/a&gt; 年就已经出现的 JavaScript 引擎，并完全由 Java 代码编写而成。后来被 JDK 1.6 收纳为官方默认的 JavaScript 引擎。这也就意味着现如今的 Java 虚拟机，都是一直可以支持 JavaScript 的。不过由于大环境（在 Java 平台上面跑 JavaScript 毕竟接受度不高）以及 Java 虚拟机对动态语言支持能力欠缺等问题，使得这件事情并没有流行起来。&lt;/p&gt;

&lt;p&gt;另外一个导致 Rhino 不够流行的原因，就是对其性能的诟病。在早期的时候 Rhino 是将 JavaScript 代码直接编译为 Java Class，运行期性能非常出色，往往可以击败很多用 C 实现的基于 JIT 技术的引擎。不过生成 Java 二进制代码和装载这些生成的类是个非常重量级的操作，需要耗费大量时间，而且编译过程产生的许多无用中间类和字符串无法被虚拟机回收掉。不过在引入了解析模式以后这些问题就得到了一定的解决。一些 &lt;a href=&quot;http://hns.github.io/2010/09/21/benchmark.html&quot;&gt;性能&lt;/a&gt; &lt;a href=&quot;http://hns.github.io/2010/09/29/benchmark2.html&quot;&gt;测试&lt;/a&gt; &lt;a href=&quot;https://www.techempower.com/benchmarks&quot;&gt;报告&lt;/a&gt; 显示出 Rhino 的性能依旧非常良好。&lt;/p&gt;

&lt;p&gt;后来 Java 官方也意识到虚拟机对动态语言欠缺支持以后，就从 JDK 1.7 开始引入了 &lt;a href=&quot;http://www.javaworld.com/article/2860079/scripting-jvm-languages/invokedynamic-101.html&quot;&gt;invokedynamic&lt;/a&gt; 的技术，可以有效提升动态语言的运行性能，而且 Oracle 也在 JDK 1.8 中自带了一个基于此技术实现的新一代的 JavaScript 引擎 &lt;a href=&quot;http://www.oracle.com/technetwork/articles/java/jf14-nashorn-2126515.html&quot;&gt;Nashorn&lt;/a&gt;，终于让性能不再是什么大问题了。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2015/javascript-in-jvm.png&quot; alt=&quot;JavaScript in JVM&quot; /&gt;&lt;/p&gt;

&lt;p&gt;在不同版本的 JDK 上运行 JavaScript，可以有不同的解决方案（如上图）。如果是在老版本的 1.6 上面，只能使用 Rhino，以及在 Rhino 之上封装的开发框架 &lt;a href=&quot;http://ringojs.org&quot;&gt;Ringo&lt;/a&gt;，这个组合里面没有对 invokedynamic 的支持。当然由于向后兼容性的存在，其实这个解决方案是可以适用于后续 1.7 和 1.8 版本的。如果是在 1.7 版本上面尝试使用 JavaScript，那么可以考虑 &lt;a href=&quot;http://dynjs.org&quot;&gt;DynJS&lt;/a&gt; 引擎，这是另外一个独立的 JavaScript 引擎实现，支持了 invokedynamic 技术，与此配套的还有一个 &lt;a href=&quot;http://nodyn.io&quot;&gt;Nodyn&lt;/a&gt; 项目，是对 Node 的一个移植。不过很可惜，这套开发栈没等流行起来，就被后来的技术给超越了，因此除非你必须要坚守在 1.7 上面，否则并不建议采用。最后，也是这个话题的终极解决方案，就是升级到 JDK 1.8 并使用官方的 Nashorn 引擎，与其对应的框架可以选用 &lt;a href=&quot;http://vertx.io&quot;&gt;Vert.x&lt;/a&gt;。Vert.x 是由 Eclipse 贡献的，实现了良好的事件驱动和异步非阻塞机制，以及 Reactive 特性，这些都是 Ringo 所没有的。不过 Oracle 官方也有一个基于 Nashorn 引擎的 Node 实现，叫做 &lt;a href=&quot;https://avatar-js.java.net&quot;&gt;avatar.js&lt;/a&gt;，不过截止到发稿时，该项目已经有一段时间没有更新过了。&lt;/p&gt;

&lt;p&gt;下面展示一些使用 JavaScript 开发 Java 应用的示例。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;small&gt;注：在尝试运行这些样例代码之前，请确认已经正确安装了 JDK 1.8u51 或以上版本、Vert.x 3.x 和 Maven 3.x 版本。&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h3 id=&quot;一操作-arraylist&quot;&gt;一、操作 ArrayList&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;util&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ArrayList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;list&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2015/arraylist.gif&quot; alt=&quot;ArrayList&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;二filter-and-reduce&quot;&gt;二、Filter and Reduce&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;5&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;6&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;7&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;9&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;10&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filtered&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filter&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;%&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;==&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;filtered&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;sumOfFiltered&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;filtered&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;reduce&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;acc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;acc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;next&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;},&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nx&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;sumOfFiltered&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2015/filter-and-reduce.gif&quot; alt=&quot;Filter and Reduce&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;三web-服务&quot;&gt;三、Web 服务&lt;/h3&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nx&quot;&gt;vertx&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createHttpServer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;requestHandler&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;req&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;response&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;putHeader&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;content-type&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;text/plain&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;end&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello World!&lt;/span&gt;&lt;span class=&quot;err&quot;&gt;\&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;n&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}).&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;listen&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;8080&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;print&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'listening on port 8080...'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2015/web-server-in-vertx.gif&quot; alt=&quot;Web server in Vert.x&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;四springstick-和-hibernate-ssh&quot;&gt;四、Spring、Stick 和 Hibernate (SSH)&lt;/h3&gt;

&lt;p&gt;这是一个比较复杂的例子，首先项目使用 Maven 来管理，并通过 jetty-maven-plugin 来运行。项目内部使用 Spring 作为整个集成的核心，用 Hibernate 来实现数据访问。Stick 是基于 Ringo 的一个组件，实现了类似 express 一样的功能。下面的这段代码，演示了如何通过 JavaScript 实现一个 router，并在 router 内部调用 Spring 和 Hibernate 的 API 实现数据查询，并返回 JSON 结果。此项目的完整源代码请在&lt;a href=&quot;https://github.com/tangrui/enterprise-javascript-shenjs2015&quot;&gt;这里&lt;/a&gt;获取。&lt;/p&gt;

&lt;div class=&quot;language-javascript highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'stick'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;require&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'ringo/jsgi/response'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;net&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;tangrui&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;shenjs&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;ringossh&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;web&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;SpringAwareJsgiServlet&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;nx&quot;&gt;exports&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;router&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Application&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
  &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;configure&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'params'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'route'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'/hello'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;html&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello World!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'/tasks'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;function&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;Context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getInstance&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;emf&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;context&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getBean&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'entityManagerFactory'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;em&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;emf&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createEntityManager&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;createQuery&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'from Task'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tasks&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;query&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;getResultList&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;[];&lt;/span&gt; 
    &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;amp;&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;lt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;size&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;task&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;tasks&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;kd&quot;&gt;get&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
      &lt;span class=&quot;kd&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;taskObj&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{};&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;taskObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;taskObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;description&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;task&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;description&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
      &lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;taskObj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;nx&quot;&gt;em&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;close&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;json&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nx&quot;&gt;result&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;});&lt;/span&gt;

  &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;nx&quot;&gt;app&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;})();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;运行该项目前，需要首先安装并配置一个 MySQL 数据库，并运行在默认的 3306 端口，root 用户的口令为 mysecretpassword。创建一个名为 ringo-ssh 的数据库，字符集为 UTF-8。然后使用如下的 SQL 语句创建一个数据库表 T_TASK，并插入一些数据：&lt;/p&gt;

&lt;div class=&quot;language-sql highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;k&quot;&gt;CREATE&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;TABLE&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`T_TASK`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`F_ID`&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NOT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`F_DESC`&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2000&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;nv&quot;&gt;`F_NAME`&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;varchar&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;NULL&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;PRIMARY&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;KEY&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`F_ID`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;ENGINE&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InnoDB&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;DEFAULT&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;CHARSET&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;utf8&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`T_TASK`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`F_ID`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`F_NAME`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`F_DESC`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'1'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'ShenJS 2015'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'别忘记在 7 月 11 日和 12 日。'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;INSERT&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;INTO&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`T_TASK`&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;`F_ID`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`F_NAME`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;`F_DESC`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;VALUES&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;'2'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'给妈妈打电话'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;'周末记得给妈妈打电话。'&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;另外，该项目依赖了我们&lt;a href=&quot;http://www.zyeeda.com&quot;&gt;公司&lt;/a&gt;的两个开源项目，分别是 &lt;a href=&quot;https://github.com/zyeeda/origin&quot;&gt;origin&lt;/a&gt; 和 &lt;a href=&quot;https://github.com/zyeeda/cdeio-runtime&quot;&gt;cdeio-runtime&lt;/a&gt;。请从 Github 上 clone 下来以后，分别进入项目目录，运行 &lt;code class=&quot;highlighter-rouge&quot;&gt;mvn clean install&lt;/code&gt; 来编译和安装。注意执行的顺序为先 origin 再 cdeio-runtime。最后，可以通过运行 &lt;code class=&quot;highlighter-rouge&quot;&gt;mvn jetty:run&lt;/code&gt; 命令来启动这个示例。&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;small&gt;注：如果是第一次运行该示例，Maven 会下载很多依赖包，这个过程根据网速快慢会长短不一，请耐心等待。&lt;/small&gt;&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;以下是系统运行后的控制台截图和 curl 的返回结果。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2015/ringo-ssh-maven.png&quot; alt=&quot;Ringo SSH Maven&quot; /&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2015/ringo-ssh-curl.png&quot; alt=&quot;Ringo SSH Curl&quot; /&gt;&lt;/p&gt;

&lt;p&gt;总之，在 Java 平台上使用 JavaScript 作为首要开发语言是完全可行的，在上面这个示例中，除了 Hibernate 必须的领域实体等是使用 Java 来编写的之外，其他的大部分逻辑都是可以用 JavaScript 完成的，而且可以跟各种 Java 中间件完美集成。有关这方面的深入介绍远不止一篇博客所能言尽，有兴趣了解更多的可以参考我公司的 &lt;del&gt;&lt;a href=&quot;http://www.zyeeda.com/platform.html&quot;&gt;CDEIO&lt;/a&gt;&lt;/del&gt; 平台。&lt;/p&gt;
</description>
        <pubDate>Fri, 31 Jul 2015 11:36:55 +0000</pubDate>
        <link>http://www.tangrui.net/2015/javascript-over-java-stack.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2015/javascript-over-java-stack.html</guid>
        
        <category>演讲</category>
        
        <category>Lightening Talk</category>
        
        <category>Java</category>
        
        <category>JavaScript</category>
        
        <category>Rhino</category>
        
        <category>Nashorn</category>
        
        <category>invokedynamic</category>
        
        <category>CDE.IO</category>
        
        <category>ShenJS 2015</category>
        
        
        <category>技术</category>
        
      </item>
    
      <item>
        <title>ShenJS 2015 那些年我们一起追过的盛典
</title>
        <description>&lt;p&gt;这还是我第一次听说过有追会的，当我第一天来到 ShenJS 的现场。&lt;/p&gt;

&lt;p&gt;深圳的天气一如既往的好，也一如既往的热。&lt;/p&gt;

&lt;p&gt;对 JSConf 其实早有耳闻，放眼全世界都算是最顶尖 JS 开发者的盛会。JSConf CN 三年前悄然兴起，我就在望眼欲穿的盼。沪——京——杭——终于，就跟 Linkin Park 要光临深圳开演唱会一样，我偶遇了传说中的 ShenJS。&lt;/p&gt;

&lt;p&gt;虽然在经济上总跟“北上广”齐名，但是在技术峰会方面，深圳永远无法望“京沪”之项背。或许是我所接触的行业不同，国内的互联网圈子或许会更活跃，更喜欢组织活动，进行分享。可惜我们是搞企业信息系统集成的——对，就是那些成天面对需求变更，被外行指导软件开发，永远延期，没有最烂只有更烂的项目外包企业——在这里，Oracle 是我们的天空，Java 是我们的空气，Apache 是我们的信仰。&lt;/p&gt;

&lt;p&gt;这似乎跟 JS 没有太大关系，除了我个人的 JS 情节。&lt;/p&gt;

&lt;p&gt;从我接触 JS 的那天起就被她深深吸引了，虽然当时根本不知道那是一种什么样的语言。那个时候 Web 开发很纯净，纯净到只有 IE 6，Firefox 还是 1.0，Chrome 还没有出生，什么 jQuery 还不流行，prototype.js 在争议与无奈中被一遍又一遍的使用，Crockford 刚刚发明 JSON，即时函数调用还在他脑子里，至于 Node.js 嘛——孩子，你说的是用来处理 DOM 的函数库吗？就这样在懵懂中我完成了职业生涯的第一个像模像样的项目，整个前端重度依赖 JavaScript。也是那个时候才知道，原来一种语言可以有那么多种风格截然不同的写法，浏览器原来有那么大的差异，IE 6 原来是那么的慢。或许正是这次项目的成功让我对 JS 产生了好感和依赖。“Any application that can be written in JavaScript, will eventually be written in JavaScript”，这个“最终”在我这里成为了必然。&lt;/p&gt;

&lt;p&gt;转眼间十年已逝，Java 和 JavaScript 都迎来了 20 周年的庆典，我走入了 ShenJS 的会场。&lt;/p&gt;

&lt;p&gt;一进门就被热烈气愤所惊骇：这是技术人员的聚会吗？程序猿会有那么嗨吗？竟然还有人是从外地专门赶来的？有人遭遇台风到不了的？竟然有神人参加过全部四届的 JSConf 大会？Oh my god，这是我想要的感觉，外加舞台上还有个这么逗逼的主持人，Oh yeah，程序猿的高潮瞬间就一泻千里了。&lt;/p&gt;

&lt;p&gt;JSConf 无愧为国内最高水平的 JS 会议，总共邀请了来自 11 个国家和地区的 21 位讲师，为大家奉上两天不间断的饕餮盛宴。先来看看菜单吧：&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2015/agenda-of-shenjs.png&quot; alt=&quot;JS 中国开发者大会 - ShenJS&quot; /&gt;&lt;/p&gt;

&lt;p&gt;主持人 Goddy Zhao 必须要提一下，他的开场和串场也真是醉了，还让单身狗明白，“找不到对象”的原因是因为写了太多的空指针。&lt;/p&gt;

&lt;p&gt;言归正传，这毕竟还是一次技术的交流会议，总得有点干货吧。&lt;/p&gt;

&lt;h3 id=&quot;一react--flux&quot;&gt;一、React + Flux&lt;/h3&gt;

&lt;p&gt;这个一点都不稀奇，这半年除了 ES6，估计就属 React 最风光无限了。Web 开发就是这样，为了解决 DOM 性能瓶颈，八仙过海，各显神通。不过 Facebook 给出的解决方案还是让社区为之一振。Virtual DOM（就是在内存中构建一棵虚拟的 DOM 树，在界面需要更新的时候，先在内存中做一次 diff，只将发生变更了的 DOM 节点应用到界面上）的引入让社区瞬间沸腾（Herman 说 React 在阿里很火），看上去多此一举的计算，却能显著提高渲染的性能。在符合要求的前提下，两棵 DOM 树的 diff 复杂度能控制在 O(n) 水平，充分体现了 Facebook “牛逼闪闪”的技术实力。就连不怎么好看的 JSX 也逐渐被爱屋及乌了，况且 React 的组件特性还能顺带解决 CSS 的&lt;a href=&quot;https://speakerdeck.com/vjeux/react-css-in-js&quot;&gt;一些固有问题&lt;/a&gt;。&lt;/p&gt;

&lt;p&gt;Flux 更是简单得一塌糊涂，简单到竟然不一定需要第三方类库来实现（当然还是会有的）。比起 MVC 和 MVVM 这样的重量级框架，React + Flux 就可以解决从数据流动到界面渲染的全套 UI 过程，而且强制要求的单向数据流，也使得架构设计变得简单许多。&lt;/p&gt;

&lt;p&gt;因为 Virtual DOM 需要先期做一次 diff，那么潜在的性能瓶颈就有可能会出现在这个地方。Facebook 何许人也，&lt;a href=&quot;https://github.com/facebook/immutable-js&quot;&gt;immutable.js&lt;/a&gt; 就好像是 React 的孪生姐妹一样，早就在那里等待大家的临幸了。这次会议上听说的唯一一个新概念就是 Persistence Data Structure，其实也可以完美的和 React 集成来实现更多炫酷的能力。我之所以对这个概念非常感冒是因为在曾经的一个项目中，客户就要求实现这样一个需求：可以将组织机构变更的历史记录下来，并随时可以回滚。这正是 PDS 的应用场景呀，当时我懂得少，把这需求给搪塞回去了。&lt;/p&gt;

&lt;p&gt;Web 开发还经常前前后后的变化。曾经的服务器比终端性能好，大部分计算在服务器上完成，以实现所谓的瘦客户端；后来终端性能提升了，为了释放服务器的计算能力，降低网络传输负载，将一部分的运算（尤其是界面渲染）转交给终端，以实现所谓的富客户端（或者那个时候的流行词 Web 2.0）；现在 React 的来临再一次提出服务器端渲染的能力，而且 isomorphic/universal 的类库还使得前后端的代码一致而通用。&lt;/p&gt;

&lt;p&gt;不过 React + Flux 也不是完全没有问题。Flux 官方对异常处理没有一个明确的方案指引，Yahoo Fluxible 的实现也通篇没提到过关于异常数据该如何流动的问题，这些还需要时间和更多的社区实践来总结跟积累了。测试方面，Jest 的性能是个严重的问题，coding.net 的同学们在单元测试的选型中就遭遇了这个问题，最终投向 Karma 的怀抱。另外我觉得 Jest 的侵入性太强，不如 Karma 有那么好的框架整合能力，对于混合使用各种技术的单元测试应该不是个明智之选。&lt;/p&gt;

&lt;h3 id=&quot;二异步流程控制&quot;&gt;二、异步流程控制&lt;/h3&gt;

&lt;p&gt;这个话题应该是 JS 社区永远都无法停止讨论的。JS 异步单线程的特性，刺激了各种最强大脑对于此问题的思考。就目前状况来看，解决方案无疑有如下几种：&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Promise&lt;/strong&gt;：ES6 先天支持的特性，ES5 也有各种第三方库可以使用，算是最容易获得的解决方案。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;co + generator&lt;/strong&gt;：这是最接近 ES7 async/await 写法的解决方案，需要 JS 运行时支持 ES6 的 generator 特性，或通过 Babel 等转译。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;thunks&lt;/strong&gt;：这是在严老师的《Koa 和 Toa 的框架原理及开发实践》课程中提到过的一种解决方案，能够友好的支持 promise，generator 和回调等各种异步返回方式。&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;async/await&lt;/strong&gt;：这个应该算是异步流程控制的终极解决方案了，可惜是 ES7 中才会出现的特性，但是依靠 Babel 尝尝鲜是不成问题的，但谁能保证最终的规范不会发生变化呢？&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;当然，重要的事情最后再说，庄恒飞同学给出的 &lt;a href=&quot;https://github.com/xicilion/fibjs&quot;&gt;fibjs&lt;/a&gt; 是以消灭这个问题的思路去解决问题的，有兴趣的大大们可以多多关注。&lt;/p&gt;

&lt;h3 id=&quot;三架构实战&quot;&gt;三、架构实战&lt;/h3&gt;

&lt;p&gt;说实在的，因为不从事互联网这种高并发、重架构的日常工作，所以对其中的一些设计没有太多感觉。而且因为没有真正遇到过问题，对其解决方案的精妙之处也就无从感知了。只是在演示中看到各种性能的提升。外行就不在这里评论了，免得被笑掉大牙。&lt;/p&gt;

&lt;h3 id=&quot;四炫酷演示&quot;&gt;四、炫酷演示&lt;/h3&gt;

&lt;p&gt;我把 90 后（尼玛，那么年轻）沈毅同学的 &lt;a href=&quot;http://echarts.baidu.com&quot;&gt;ECharts&lt;/a&gt; 课程放到炫酷演示里面应该毫不为过，顺便也刺激一下身为东道主的企鹅公司，就算不开 session，也好歹赞助几个公仔吧。百度在前端领域为社区做出的贡献是有目共睹的，我们公司在日常开发中使用的唯一一个国产开源产品就是百度 FEX 团队出品的 F.I.S。当然这个课程并不是纯粹的演示，还是深入介绍了一些难点问题的解决思路和实现原理，本人对 canvas 及相关内容不甚熟悉，也不妄加评论了。&lt;/p&gt;

&lt;p&gt;真正的炫酷演示还是来自于三个老外。感觉国内大牛们分享的大多是跟工作有关的内容，或者或多或少会在工作中碰到或用到的；而国外的牛牛们似乎更偏重娱乐与兴趣，不知是环境差异还是文化使然。&lt;/p&gt;

&lt;p&gt;三个炫酷演示都是跟硬件有关，也再一次证明了 JS 作为全栈语言的强大实力。曾几何时，硬件的开发都永远是阳春白雪，束之高阁的。什么汇编还有 C，都不是普通码农所能靠近的。然而 JS 正在悄悄的改变着这一切。Arduino 作为标准的开源硬件，JS 作为最广泛使用的编程语言，他们的结合剩下的就是想象力的无限放飞。使用 &lt;a href=&quot;https://github.com/rwaldron/johnny-five&quot;&gt;Johnny Five&lt;/a&gt; 就可以通过电脑连接 Arduino 而实现用 JS 控制硬件的能力，Andrew 的 Nodebot 就是这么一个原理。Tim 的 NERD DISCO 更是使用了浏览器的 Web Audio API，在浏览器上实现了声音可视化的效果。其实技术都不是问题，音乐 + 灯光 + DJ 调音器，呈献给现场的就是一段炫爆了的电子 DISCO 秀，真是”不会写 JS 的 DJ 不是好讲师呀”。&lt;/p&gt;

&lt;p&gt;有了声音自然还要有图像，平面的图像电影院都不怎么放了，3D 的东西也见太多了，VR 是近些年红的发紫的新鲜事物，ShenJS 也真是给力，Martin 关于 WebGL 和 WebVR 的介绍也算是让我们这些无法接触到像 Google Cardboard 这类玩意儿的天朝子民能开开眼界了。而且他现场演示使用的 &lt;a href=&quot;https://github.com/brianpeiris/RiftSketch&quot;&gt;IDE&lt;/a&gt; 真是与众不同，实时写代码，还能及时看效果，关键是现场写程序调 3D 图形建模，可见功底之扎实呀，曾几何时，我们学过的这些知识都还给大学老师了。&lt;/p&gt;

&lt;p&gt;Martias 的 p2p pipes 现场写代码，佩服佩服，讲什么都不重要了，但是老外们的 JS 都不写分号的吗？&lt;/p&gt;

&lt;h3 id=&quot;五其他&quot;&gt;五、其他&lt;/h3&gt;

&lt;p&gt;剩下的这些就不知道怎么分类了。&lt;/p&gt;

&lt;p&gt;PM2 和 Keymatrix.io 的介绍，说实在的下午第一节，我基本上半睡半醒，只知道功能确实强大，还给出了 ShenJS2015 一个七折的优惠码。当然最令人兴奋的还是作为讲师的 Alexandre 竟然是 PM2 的作者，“敬仰犹如滔滔江水连绵不绝，又如黄河泛滥一发而不可收拾”。&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/static/uploads/2015/javascript-best.jpg&quot; alt=&quot;JavaScript 是最好的编程语言&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Hax 的 JavaScript: The World’s Best Programming Language，我不得不先上图了，因为群里已经有人将此作为表情了。我在这里想评论的一点就是，这明显是一群 JSer 在一起意淫的结果嘛，但是那又怎样，JavaScript 专治各种不服。&lt;/p&gt;

&lt;p&gt;Lighting Talk 是个有意思的环节，每个程序猿都有分享的意愿，这里就能满足你“好为人师”的心理。两天的小讲座内容着实不少：有介绍安全漏洞的，有介绍自己小产品的，有诉说开发经验的等等，太多了没都记下来。让我印象深刻的是华大基因的介绍，因为以前在 Amazon AWSome Day 的活动中，华大基因对 AWS 的应用也是作为典型案例被介绍的（这算广告吗，能给点赞助费吗）。当然还有我自己分享的关于 Enterprise JavaScript 的内容，可惜时间太短了，又是个大话题，准备的一些 Demo 都没来得及演示。后面抽空写个博客详细展开一下。&lt;/p&gt;

&lt;p&gt;最后的最后，以 Herman 老师的课程作为结束吧，虽然全篇没太注意他在讲什么，但就是觉得印象深刻。Herman 老师的前端开发摩尔定律更是令人俯首称臣呀：每 18 到 24 个月，前端开发的难度就会增加一倍！干！&lt;/p&gt;

&lt;p&gt;因为住的地方离南山比较远，所以两天的 Afterparty 都没有参加，尤其是 Nodebot 的分组对抗，尤其遗憾。后面又听群里说周日的派对上面很多人聊得非常投机，也没有机会参与。这是我第一次参加这种规模的技术峰会，收益颇深。难怪会有这么多忠实粉丝不远万里、不怕险阻、不惧台风、不辞艰辛的来执着追随着这个会议。或许所有 JSer 的内心都跟 JS 本身一样无比复杂，又无比单纯。回归生活我们是被人揶揄的程序猿，在这里我们是崇尚自由的乐天派。不论下一届的 JSConf 开在哪里，相信永远会是“那些年我们一起追过的盛典”。&lt;/p&gt;

&lt;p&gt;版权所有，欢迎转载！&lt;/p&gt;
</description>
        <pubDate>Tue, 14 Jul 2015 14:56:23 +0000</pubDate>
        <link>http://www.tangrui.net/2015/shenjs-2015.html</link>
        <guid isPermaLink="true">http://www.tangrui.net/2015/shenjs-2015.html</guid>
        
        <category>活动</category>
        
        <category>ShenJS 2015</category>
        
        <category>JSConf</category>
        
        
        <category>其他</category>
        
      </item>
    
  </channel>
</rss>
