bugstack虫洞栈 | 沉淀、分享、成长,让自己和他人都能有所收获 沉淀、分享、成长,专注于原创专题案例,以最易学习编程的方式分享知识,让自己和他人都能有所收获。目前已完成的专题有;Netty4.x实战专题案例、用Java实现JVM、基于JavaAgent的全链路监控、手写RPC框架、DDD专题案例等。 https://bugstack.cn/ Tue, 19 Oct 2021 08:15:12 +0800 Tue, 19 Oct 2021 08:15:12 +0800 Jekyll v4.0.0 《IntelliJ IDEA 插件开发》第一节:两种方式创建插件工程 <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a> <br />原文:<a href=""></a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一前言">一、前言</h2> <p><code class="highlighter-rouge">并不是所有的不会,都是真不会!</code></p> <p>对于码农这一行业的编程学习生涯来说,会遇到很多的<strong>不会</strong>,不会搭建IDEA工程、不会写老师的案例、不会完成书中的效果、不会做项目的需求、不会实现复杂的逻辑、不会抽象工程的结构等等。但这些不会当中并不是所有的不会,都因为太复杂学不会,而是很大一部分内容因为找不到好的资料、没有清晰的文档、缺少完整的案例,导致不知道所以不会。</p> <p>正好最近在折腾IDEA插件开发的时候,市面的资料确实不多,也没有成体系完整的开发指导手册,所以就遇到了很多不知道就不会的事情,需要一点点查询搜索源码、验证API接口,最终把各项功能实现,当然在这个过程中也确实踩了不少坑!</p> <p>好!沉淀下来,接下来在这个专栏会把一些关于 IDEA 插件开发用到的各项知识做成案例输出出来,一方面可以让自己缕清所有的知识项,另一方面也可以帮助到更多的有需要的研发人员使用。</p> <h2 id="二需求目的">二、需求目的</h2> <p>可能你会想什么场景会需要用到插件开发,其实插件开发算是一种通用的解决方案,由服务平台定义标准让各自使用方进行自需的扩展。</p> <p>这就像我们非常常用的 P3C 代码检查插件、代码审计插件、脚手架工程创建插件、自动化API提取插件、单元测试统计插件等等,这些都是在 IDEA 代码开发平台扩展出来的各项功能插件。</p> <p>插件也可以说是一种解决方案,其实与你在代码编程时使用人家已经定义好的标准结构和功能下,扩展出自己的功能时是一样的。而这种方式也可以非常好的解决一些属于代码开发期间不易于放到代码提测后问题场景,并能及时提醒研发人员作出响应的修改处理。</p> <h2 id="三环境说明">三、环境说明</h2> <ul> <li>IntelliJ Platform Plugin JDK <em>不是自己安装的JDK1.8等,只有插件JDK才能开发插件</em></li> <li>IntelliJ IDEA 2019.3.1 x64 <em>如果你是其他版本,会涉及到 插件工程创建后版本修改</em></li> <li>gradle-5.2.1 <em>与 2019 IDEA 版本下的插件开发匹配,如果遇到一些环境问题可以参考我们开篇介绍</em></li> </ul> <p>在官方文档 <a href="https://plugins.jetbrains.com/docs/intellij/disposers.html">https://plugins.jetbrains.com/docs/intellij/disposers.html</a> 介绍开发 IDEA 插件的工程方式有两种,分别是模板方式和 Gradle 工程方式。这里我们分别演示下不同方式下工程的创建和所涉及到知识点内容的介绍,虽然两种方式都能创建 IDEA 插件工程,但更推荐使用 Gradle 方式。</p> <h2 id="四模板方式创建">四、模板方式创建</h2> <h3 id="1-创建引导">1. 创建引导</h3> <p><strong>New -&gt; Project -&gt; IntelliJ Platform Plugin</strong></p> <p><img src="https://bugstack.cn/assets/images/middleware/guide-idea-plugin-1-01.png" alt="" /></p> <h3 id="2-工程结构">2. 工程结构</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">guide</span><span class="o">-</span><span class="n">idea</span><span class="o">-</span><span class="n">plugin</span><span class="o">-</span><span class="n">create</span><span class="o">-</span><span class="n">project</span><span class="o">-</span><span class="n">by</span><span class="o">-</span><span class="n">platform</span> <span class="err">├──</span> <span class="n">resources</span> <span class="err">│</span> <span class="err">└──</span> <span class="no">META</span><span class="o">-</span><span class="no">INF</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">plugin</span><span class="o">.</span><span class="na">xml</span> <span class="err">└──</span> <span class="n">src</span> <span class="err">└──</span> <span class="n">cn</span><span class="o">.</span><span class="na">bugstack</span><span class="o">.</span><span class="na">guide</span><span class="o">.</span><span class="na">idea</span><span class="o">.</span><span class="na">plugin</span> <span class="err">└──</span> <span class="nc">MyAction</span><span class="o">.</span><span class="na">java</span> </code></pre></div></div> <p><strong>源码</strong>:<a href="https://github.com/fuzhengwei/guide-idea-plugin-create-project-by-platform">https://github.com/fuzhengwei/guide-idea-plugin-create-project-by-platform</a></p> <ul> <li>plugin.xml 插件配置:开发描述、版本信息、Action事件入口、扩展信息(数据存放等)</li> <li>src 具体的事件、UI窗体、工程逻辑代码开发</li> <li>另外类似 MyAction 的创建并不是直接创建普通类,而是通过 <strong>New -&gt; Plugin DevKit -&gt; Action</strong> 的方式进行创建,因为这样的创建方式可以再 plugin.xml 中自动添加 action 配置。<em>当然如果你要是自己手动创建普通类那样创建 Action 类,则需要自己手动处理配置信息。</em></li> </ul> <h3 id="3-pluginxml-配置">3. plugin.xml 配置</h3> <div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;idea-plugin&gt;</span> <span class="nt">&lt;id&gt;</span>cn.bugstack.guide.idea.plugin<span class="nt">&lt;/id&gt;</span> <span class="nt">&lt;name&gt;</span>CreateProjectByPlatform<span class="nt">&lt;/name&gt;</span> <span class="nt">&lt;version&gt;</span>1.0<span class="nt">&lt;/version&gt;</span> <span class="nt">&lt;vendor</span> <span class="na">email=</span><span class="s">"[email protected]"</span> <span class="na">url=</span><span class="s">"https://bugstack.cn"</span><span class="nt">&gt;</span>小傅哥<span class="nt">&lt;/vendor&gt;</span> <span class="nt">&lt;description&gt;</span><span class="cp">&lt;![CDATA[ 基于IDEA插件模板方式创建测试工程&lt;br&gt;</span> <span class="nt">&lt;em&gt;</span>1. 学习IDEA插件工程搭建<span class="nt">&lt;/em&gt;</span> <span class="nt">&lt;em&gt;</span>2. 验证插件基础功能实现<span class="nt">&lt;/em&gt;</span> ]]&gt;<span class="nt">&lt;/description&gt;</span> <span class="nt">&lt;change-notes&gt;</span><span class="cp">&lt;![CDATA[ 插件开发学习功能点&lt;br&gt;</span> <span class="nt">&lt;em&gt;</span>1. 工程搭建<span class="nt">&lt;/em&gt;</span> <span class="nt">&lt;em&gt;</span>2. 菜单读取<span class="nt">&lt;/em&gt;</span> <span class="nt">&lt;em&gt;</span>3. 获取配置<span class="nt">&lt;/em&gt;</span> <span class="nt">&lt;em&gt;</span>4. 回显页面<span class="nt">&lt;/em&gt;</span> ]]&gt; <span class="nt">&lt;/change-notes&gt;</span> <span class="c">&lt;!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html for description --&gt;</span> <span class="nt">&lt;idea-version</span> <span class="na">since-build=</span><span class="s">"173.0"</span><span class="nt">/&gt;</span> <span class="c">&lt;!-- please see http://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/plugin_compatibility.html on how to target different products --&gt;</span> <span class="nt">&lt;depends&gt;</span>com.intellij.modules.platform<span class="nt">&lt;/depends&gt;</span> <span class="nt">&lt;extensions</span> <span class="na">defaultExtensionNs=</span><span class="s">"com.intellij"</span><span class="nt">&gt;</span> <span class="c">&lt;!-- Add your extensions here --&gt;</span> <span class="nt">&lt;/extensions&gt;</span> <span class="nt">&lt;actions&gt;</span> <span class="c">&lt;!-- Add your actions here --&gt;</span> <span class="nt">&lt;action</span> <span class="na">id=</span><span class="s">"MyAction"</span> <span class="na">class=</span><span class="s">"cn.bugstack.guide.idea.plugin.MyAction"</span> <span class="na">text=</span><span class="s">"MyAction"</span> <span class="na">description=</span><span class="s">"MyAction"</span><span class="nt">&gt;</span> <span class="nt">&lt;add-to-group</span> <span class="na">group-id=</span><span class="s">"FileMenu"</span> <span class="na">anchor=</span><span class="s">"first"</span><span class="nt">/&gt;</span> <span class="nt">&lt;/action&gt;</span> <span class="nt">&lt;/actions&gt;</span> <span class="nt">&lt;/idea-plugin&gt;</span> </code></pre></div></div> <ul> <li>这里重点看 actions 其他上面的工程信息、版本描述、个人资料都按照自己的信息填写就行,不会影响插件运行。</li> <li>actions 下是关于所有事件入口的配置,也就是你希望让你的 IDEA 插件在 IDEA 窗体中什么地方展示,以及配置快捷键等。这里的配置说明是在 FileMenu 下的第一个入口即为你的插件。</li> </ul> <h3 id="4-myaction-事件入口">4. MyAction 事件入口</h3> <p><img src="https://bugstack.cn/assets/images/middleware/guide-idea-plugin-1-04.png" alt="" /></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyAction</span> <span class="kd">extends</span> <span class="nc">AnAction</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">actionPerformed</span><span class="o">(</span><span class="nc">AnActionEvent</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Project</span> <span class="n">project</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="nc">PlatformDataKeys</span><span class="o">.</span><span class="na">PROJECT</span><span class="o">);</span> <span class="nc">PsiFile</span> <span class="n">psiFile</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="nc">CommonDataKeys</span><span class="o">.</span><span class="na">PSI_FILE</span><span class="o">);</span> <span class="nc">String</span> <span class="n">classPath</span> <span class="o">=</span> <span class="n">psiFile</span><span class="o">.</span><span class="na">getVirtualFile</span><span class="o">().</span><span class="na">getPath</span><span class="o">();</span> <span class="nc">Messages</span><span class="o">.</span><span class="na">showMessageDialog</span><span class="o">(</span><span class="n">project</span><span class="o">,</span> <span class="s">"guide-idea-plugin-create-project-by-platform: "</span> <span class="o">+</span> <span class="n">classPath</span><span class="o">,</span> <span class="s">"Hi IDEA Plugin"</span><span class="o">,</span> <span class="nc">Messages</span><span class="o">.</span><span class="na">getInformationIcon</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>在 MyAction 事件入口中获取 Project 工程信息、PsiFile 文件信息,以及对应的类路径。</li> <li>最后在 Messages.showMessageDialog 下打印,这样把鼠标放到工程类下,在点这个按钮的时候就可以看到类的路径弹窗了。</li> </ul> <h3 id="5-运行测试">5. 运行测试</h3> <p><strong>运行过程</strong></p> <ul> <li>点击 Plugin 绿色箭头,和正常启动程序一样</li> <li>这个时候它会打开一个新的 IDEA 工程,并在这个工程中默认安装你开发好的插件</li> <li>在新打开的 IDEA 插件工程中,选中工程类后,点击 File -&gt; MyAction</li> </ul> <p><strong>运行结果</strong></p> <p><img src="https://bugstack.cn/assets/images/middleware/guide-idea-plugin-1-04.png" alt="" /></p> <ul> <li>通过测试运行效果可以看到,已经可以打出工程下类的路径信息了。<em>你也可以尝试把Action的入口放到其他按钮下进行测试</em></li> </ul> <h2 id="五gradle-方式创建">五、Gradle 方式创建</h2> <h3 id="1-创建引导-1">1. 创建引导</h3> <p><strong>New -&gt; Project -&gt; Gradle 选中 Java &amp; IntelliJ Platform Plugin</strong></p> <p><img src="https://bugstack.cn/assets/images/middleware/guide-idea-plugin-1-03.png" alt="" /></p> <h3 id="2-工程结构-1">2. 工程结构</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">guide</span><span class="o">-</span><span class="n">idea</span><span class="o">-</span><span class="n">plugin</span><span class="o">-</span><span class="n">create</span><span class="o">-</span><span class="n">project</span><span class="o">-</span><span class="n">by</span><span class="o">-</span><span class="n">gradle</span> <span class="err">├──</span> <span class="o">.</span><span class="na">gradle</span> <span class="err">└──</span> <span class="n">src</span> <span class="err">├──</span> <span class="n">main</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">java</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">cn</span><span class="o">.</span><span class="na">bugstack</span><span class="o">.</span><span class="na">guide</span><span class="o">.</span><span class="na">idea</span><span class="o">.</span><span class="na">plugin</span> <span class="err">│</span> <span class="err">└──</span> <span class="nc">MyAction</span><span class="o">.</span><span class="na">java</span> <span class="err">├──</span> <span class="n">resources</span> <span class="err">│</span> <span class="err">└──</span> <span class="no">META</span><span class="o">-</span><span class="no">INF</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">plugin</span><span class="o">.</span><span class="na">xml</span> <span class="err">├──</span> <span class="n">build</span><span class="o">.</span><span class="na">gradle</span> <span class="err">└──</span> <span class="n">gradle</span><span class="o">.</span><span class="na">properties</span> </code></pre></div></div> <p><strong>源码</strong>:<a href="https://github.com/fuzhengwei/guide-idea-plugin-create-project-by-gradle">https://github.com/fuzhengwei/guide-idea-plugin-create-project-by-gradle</a></p> <ul> <li>与模板方式创建 Gradle 主要差异在 build.gradle、gradle.properties 内容的配置,这两个文件主要是处理 Gradle 相关信息的,其中 gradle.properties 用于配置 JVM Xmx 参数的,避免下载耗费资源较大崩溃。</li> <li>plugin.xml 配置插件入口等内容,MyAction 是事件入口。</li> </ul> <h3 id="3-buildgradle-配置">3. build.gradle 配置</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">plugins</span> <span class="o">{</span> <span class="n">id</span> <span class="err">'</span><span class="n">java</span><span class="err">'</span> <span class="n">id</span> <span class="err">'</span><span class="n">org</span><span class="o">.</span><span class="na">jetbrains</span><span class="o">.</span><span class="na">intellij</span><span class="err">'</span> <span class="n">version</span> <span class="err">'</span><span class="mf">0.6</span><span class="o">.</span><span class="mi">3</span><span class="err">'</span> <span class="o">}</span> <span class="n">group</span> <span class="err">'</span><span class="n">cn</span><span class="o">.</span><span class="na">bugstack</span><span class="o">.</span><span class="na">guide</span><span class="o">.</span><span class="na">idea</span><span class="o">.</span><span class="na">plugin</span><span class="err">'</span> <span class="n">version</span> <span class="err">'</span><span class="mf">1.0</span><span class="o">-</span><span class="no">SNAPSHOT</span><span class="err">'</span> <span class="n">sourceCompatibility</span> <span class="o">=</span> <span class="mf">1.8</span> <span class="n">repositories</span> <span class="o">{</span> <span class="n">mavenCentral</span><span class="o">()</span> <span class="o">}</span> <span class="n">dependencies</span> <span class="o">{</span> <span class="n">testCompile</span> <span class="nl">group:</span> <span class="err">'</span><span class="n">junit</span><span class="err">'</span><span class="o">,</span> <span class="nl">name:</span> <span class="err">'</span><span class="n">junit</span><span class="err">'</span><span class="o">,</span> <span class="nl">version:</span> <span class="err">'</span><span class="mf">4.12</span><span class="err">'</span> <span class="o">}</span> <span class="c1">// See https://github.com/JetBrains/gradle-intellij-plugin/</span> <span class="n">intellij</span> <span class="o">{</span> <span class="n">version</span> <span class="err">'</span><span class="mf">2019.3</span><span class="o">.</span><span class="mi">1</span><span class="err">'</span> <span class="o">}</span> <span class="n">patchPluginXml</span> <span class="o">{</span> <span class="n">changeNotes</span> <span class="s">""" &lt;![CDATA[ 插件开发学习功能点&lt;br&gt; &lt;em&gt;1. 工程搭建&lt;/em&gt; &lt;em&gt;2. 菜单读取&lt;/em&gt; &lt;em&gt;3. 获取配置&lt;/em&gt; &lt;em&gt;4. 回显页面&lt;/em&gt; ]]&gt;"""</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>gradle 与 maven 的使用配置上,还是可以相通的找到一些类似的地方的,如果没有使用过 gradle 也是可以使用的。</li> <li>这里需要注意 plugins 中 <code class="highlighter-rouge">id 'org.jetbrains.intellij' version '0.6.3'</code> 默认创建工程的版本有点高,与 gradle 5.x 不匹配。<em>不过你可以尝试调试合适版本进行使用</em></li> </ul> <h3 id="4-myaction-事件入口-1">4. MyAction 事件入口</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyAction</span> <span class="kd">extends</span> <span class="nc">AnAction</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">actionPerformed</span><span class="o">(</span><span class="nc">AnActionEvent</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Project</span> <span class="n">project</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="nc">PlatformDataKeys</span><span class="o">.</span><span class="na">PROJECT</span><span class="o">);</span> <span class="nc">PsiFile</span> <span class="n">psiFile</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="nc">CommonDataKeys</span><span class="o">.</span><span class="na">PSI_FILE</span><span class="o">);</span> <span class="nc">String</span> <span class="n">classPath</span> <span class="o">=</span> <span class="n">psiFile</span><span class="o">.</span><span class="na">getVirtualFile</span><span class="o">().</span><span class="na">getPath</span><span class="o">();</span> <span class="nc">Messages</span><span class="o">.</span><span class="na">showMessageDialog</span><span class="o">(</span><span class="n">project</span><span class="o">,</span> <span class="s">"guide-idea-plugin-create-project-by-gradle: "</span> <span class="o">+</span> <span class="n">classPath</span><span class="o">,</span> <span class="s">"Hi IDEA Plugin"</span><span class="o">,</span> <span class="nc">Messages</span><span class="o">.</span><span class="na">getInformationIcon</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>这里与模板方式创建的案例是一样的,为了区别两个插件测试,我们这里打印了工程的名称。当然你也可以使用 project.getName() 获取工程名称。</li> </ul> <h3 id="5-运行测试-1">5. 运行测试</h3> <ul> <li>Gradle 测试运行相当于是运行 <code class="highlighter-rouge">:runIde</code> ,也是和普通的代码调试一样。</li> </ul> <p><strong>运行结果</strong></p> <p><img src="https://bugstack.cn/assets/images/middleware/guide-idea-plugin-1-05.png" alt="" /></p> <ul> <li>通过测试运行效果可以看到,已经可以打出工程下类的路径信息了。</li> </ul> <h2 id="六总结">六、总结</h2> <ul> <li>整篇内容的学习还是蛮简单的,哪怕你之前没开发过 IDEA 插件,按照这样的套路往下折腾也是可以完成插件开发的。类似这样的知识内容只是平时常做业务开发所以接触的也不多,乍一听上去还是很陌生的,不过有这样的资料就可以上手了。</li> <li>本章节初步介绍 IDEA 插件的方式和一个非常简单的基本功能,后续我们在 Gradle 创建插件的基础上,继续开发其他案例功能,逐步学习 IDEA 插件开发用到的各项技巧用于完成所需要解决的问题。</li> <li>在学习的过程中可以自行尝试扩展一些其他组件入口,打印不同的工程信息。就像你使用的一些的插件一样,帮助你生成get、set,或者提取采集接口信息,也包括你写了多少行代码,思考它们是如何实现的。</li> </ul> <h2 id="七系列推荐">七、系列推荐</h2> <ul> <li><a href="https://bugstack.cn/itstack-demo-any/2021/09/27/p3c-%E6%8F%92%E4%BB%B6-%E6%98%AF%E6%80%8E%E4%B9%88%E6%A3%80%E6%9F%A5%E5%87%BA%E4%BD%A0%E9%82%A3%E5%B1%8E%E5%B1%B1%E7%9A%84%E4%BB%A3%E7%A0%81.html">p3c 插件,是怎么检查出你那屎山的代码?</a></li> <li><a href="https://bugstack.cn/itstack-ark-middleware/2021/08/27/%E6%8A%80%E6%9C%AF%E8%B0%83%E7%A0%94-IDEA-%E6%8F%92%E4%BB%B6%E6%80%8E%E4%B9%88%E5%BC%80%E5%8F%91.html">技术调研,IDEA 插件怎么开发「脚手架、低代码可视化编排、接口生成测试」?</a></li> <li><a href="https://bugstack.cn/framework/2021/02/04/%E5%9F%BA%E4%BA%8EIDEA%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%92%8C%E5%AD%97%E8%8A%82%E7%A0%81%E6%8F%92%E6%A1%A9%E6%8A%80%E6%9C%AF-%E5%AE%9E%E7%8E%B0%E7%A0%94%E5%8F%91%E4%BA%A4%E4%BB%98%E8%B4%A8%E9%87%8F%E8%87%AA%E5%8A%A8%E5%88%86%E6%9E%90.html">基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2021/05/09/%E5%A4%A7%E5%AD%A6%E6%AF%95%E4%B8%9A%E8%A6%81%E5%86%99%E5%A4%9A%E5%B0%91%E8%A1%8C%E4%BB%A3%E7%A0%81-%E6%89%8D%E8%83%BD%E4%B8%8D%E7%94%A8%E8%8A%B1%E9%92%B1%E5%9F%B9%E8%AE%AD%E5%B0%B1%E6%89%BE%E5%88%B0%E4%B8%80%E4%BB%BD%E5%BC%80%E5%8F%91%E5%B7%A5%E4%BD%9C.html">大学毕业要写多少行代码,才能不用花钱培训就找到一份开发工作?</a></li> <li><a href="https://bugstack.cn/interview/2021/01/26/Java%E9%9D%A2%E7%BB%8F%E6%89%8B%E5%86%8CPDF%E4%B8%8B%E8%BD%BD.html">《Java 面经手册》PDF,全书 417 页 11.5 万字,完稿&amp;发版!</a></li> </ul> Mon, 18 Oct 2021 00:00:00 +0800 https://bugstack.cn/itstack-ark-middleware/2021/10/18/IntelliJ-IDEA-%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91-%E7%AC%AC%E4%B8%80%E8%8A%82-%E4%B8%A4%E7%A7%8D%E6%96%B9%E5%BC%8F%E5%88%9B%E5%BB%BA%E6%8F%92%E4%BB%B6%E5%B7%A5%E7%A8%8B.html https://bugstack.cn/itstack-ark-middleware/2021/10/18/IntelliJ-IDEA-%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91-%E7%AC%AC%E4%B8%80%E8%8A%82-%E4%B8%A4%E7%A7%8D%E6%96%B9%E5%BC%8F%E5%88%9B%E5%BB%BA%E6%8F%92%E4%BB%B6%E5%B7%A5%E7%A8%8B.html java itstack-ark-middleware itstack-ark-middleware 12种 vo2dto 方法,就 BeanUtils.copyProperties 压测最拉胯! <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a> <br />原文:<a href="https://mp.weixin.qq.com/s/Xq7oQg7dYESMYxHVnxX8Dw">https://mp.weixin.qq.com/s/Xq7oQg7dYESMYxHVnxX8Dw</a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一前言">一、前言</h2> <p><code class="highlighter-rouge">为哈么,你的代码也就仅仅是能用而已?</code></p> <p>没有技术深度、短缺知识储备、匮乏经验积累的前提下,怎么写代码?百度呀,遇到问题这搜一点,那查一块,不管它是什么原理还是适合哪种场景,先粘贴到自己的工程里,<strong>看,能跑了,能跑就行</strong>。那这样的代码也就仅仅是能用程度的交付,根本没有一定的质量保证,也更别提数据结构、算法逻辑和设计模式了,那看的编程资料刷的LeetCode,全歇菜了。</p> <p>当你感觉看了很多资料又不会用的时候,会说什么,<code class="highlighter-rouge">真卷,都学到这样了</code>。但其实我并不觉对技术的深度挖掘、梳理全套的知识体系,一点点耕耘一点点收获<strong>是在卷</strong>。反而把看技术视频当成看电影一样轻松,不写案例就以为书看会了的爽,没有意义的缺少脑力思考机械式体力重复,才是卷,甚至很卷。</p> <p>就像让你用一个属性拷贝工具,把<code class="highlighter-rouge">vo转成dto</code>,你用了哪呢,是 Apache 的还是 Spring 的,还是其他的什么,哪个效率最高?<em>接下来我们来用数据验证下,并提供出各种案例的使用对比</em></p> <h2 id="二性能测试对比">二、性能测试对比</h2> <p>在 Java 系统工程开发过程中,都会有各个层之间的对象转换,比如 VO、DTO、PO、VO 等,而如果都是手动<code class="highlighter-rouge">get、set</code>又太浪费时间,还可能操作错误,所以选择一个自动化工具会更加方便。</p> <p>目前我整理出,用于对象属性转换有12种,包括:普通的getset、json2Json、Apache属性拷贝、Spring属性拷贝、bean-mapping、bean-mapping-asm、BeanCopier、Orika、Dozer、ModelMapper、JMapper、MapStruct 接下来我们分别测试这11种属性转换操作分别在<code class="highlighter-rouge">一百次</code>、<code class="highlighter-rouge">一千次</code>、<code class="highlighter-rouge">一万次</code>、<code class="highlighter-rouge">十万次</code>、<code class="highlighter-rouge">一百万次</code>时候的性能时间对比。</p> <p><img src="https://bugstack.cn/assets/images/guide/guide-1-01.png" alt="" /></p> <ul> <li><code class="highlighter-rouge">BeanUtils.copyProperties</code> 是大家代码里最常出现的工具类,但只要你不把它用错成 <code class="highlighter-rouge">Apache</code> 包下的,而是使用 Spring 提供的,就基本还不会对性能造成多大影响。</li> <li>但如果说性能更好,可替代手动<code class="highlighter-rouge">get、set</code>的,还是 <code class="highlighter-rouge">MapStruct</code> 更好用,因为它本身就是在编译期生成<code class="highlighter-rouge">get、set</code>代码,和我们写<code class="highlighter-rouge">get、set</code>一样。</li> <li>其他一些组件包主要基于 <code class="highlighter-rouge">AOP</code>、<code class="highlighter-rouge">ASM</code>、<code class="highlighter-rouge">CGlib</code>,的技术手段实现的,所以也会有相应的性能损耗。</li> </ul> <h2 id="三12种转换案例">三、12种转换案例</h2> <p><img src="https://bugstack.cn/assets/images/guide/guide-1-02.png" alt="" /></p> <p><strong>源码</strong>:<a href="https://github.com/fuzhengwei/guide-vo2dto">https://github.com/fuzhengwei/guide-vo2dto</a></p> <p><strong>描述</strong>:在案例工程下创建 interfaces.assembler 包,定义 <code class="highlighter-rouge">IAssembler&lt;SOURCE, TARGET&gt;#sourceToTarget(SOURCE var)</code> 接口,提供不同方式的对象转换操作类实现,学习的过程中可以直接下载运行调试。</p> <h3 id="1-getset">1. get\set</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">GetSetAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="nc">UserDTO</span> <span class="n">userDTO</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserDTO</span><span class="o">();</span> <span class="n">userDTO</span><span class="o">.</span><span class="na">setUserId</span><span class="o">(</span><span class="kt">var</span><span class="o">.</span><span class="na">getUserId</span><span class="o">());</span> <span class="n">userDTO</span><span class="o">.</span><span class="na">setUserNickName</span><span class="o">(</span><span class="kt">var</span><span class="o">.</span><span class="na">getUserNickName</span><span class="o">());</span> <span class="n">userDTO</span><span class="o">.</span><span class="na">setCreateTime</span><span class="o">(</span><span class="kt">var</span><span class="o">.</span><span class="na">getCreateTime</span><span class="o">());</span> <span class="k">return</span> <span class="n">userDTO</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>推荐</strong>:★★★☆☆</li> <li><strong>性能</strong>:★★★★★</li> <li><strong>手段</strong>:手写</li> <li><strong>点评</strong>:其实这种方式也是日常使用的最多的,性能肯定是杠杠的,就是操作起来有点麻烦。尤其是一大堆属性的 VO 对象转换为 DTO 对象时候。但其实也有一些快捷的操作方式,比如你可以通过 Shift+Alt 选中所有属性,Shift+Tab 归并到一列,接下来在使用 Alt 选中这一列,批量操作粘贴 <code class="highlighter-rouge">userDTO.set</code> 以及快捷键大写属性首字母,最后切换到结尾补充括号和分号,最终格式化一下就搞定了。</li> </ul> <h3 id="2-json2json">2. json2Json</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">Json2JsonAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">strJson</span> <span class="o">=</span> <span class="no">JSON</span><span class="o">.</span><span class="na">toJSONString</span><span class="o">(</span><span class="kt">var</span><span class="o">);</span> <span class="k">return</span> <span class="no">JSON</span><span class="o">.</span><span class="na">parseObject</span><span class="o">(</span><span class="n">strJson</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>推荐</strong>:☆☆☆☆☆</li> <li><strong>性能</strong>:★☆☆☆☆</li> <li><strong>手段</strong>:把对象转JSON串,再把JSON转另外一个对象</li> <li><strong>点评</strong>:这么写多半有点烧!</li> </ul> <h3 id="3-apache-copyproperties">3. Apache copyProperties</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">ApacheCopyPropertiesAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="nc">UserDTO</span> <span class="n">userDTO</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserDTO</span><span class="o">();</span> <span class="k">try</span> <span class="o">{</span> <span class="nc">BeanUtils</span><span class="o">.</span><span class="na">copyProperties</span><span class="o">(</span><span class="n">userDTO</span><span class="o">,</span> <span class="kt">var</span><span class="o">);</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">IllegalAccessException</span> <span class="o">|</span> <span class="nc">InvocationTargetException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="n">e</span><span class="o">.</span><span class="na">printStackTrace</span><span class="o">();</span> <span class="o">}</span> <span class="k">return</span> <span class="n">userDTO</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>推荐</strong>:☆☆☆☆☆</li> <li><strong>性能</strong>:★☆☆☆☆</li> <li><strong>手段</strong>:Introspector 机制获取到类的属性来进行赋值操作</li> <li><strong>点评</strong>:有坑,兼容性交差,不建议使用</li> </ul> <h3 id="4-spring-copyproperties">4. Spring copyProperties</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">SpringCopyPropertiesAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="nc">UserDTO</span> <span class="n">userDTO</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserDTO</span><span class="o">();</span> <span class="nc">BeanUtils</span><span class="o">.</span><span class="na">copyProperties</span><span class="o">(</span><span class="kt">var</span><span class="o">,</span> <span class="n">userDTO</span><span class="o">);</span> <span class="k">return</span> <span class="n">userDTO</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>推荐</strong>:★★★☆☆</li> <li><strong>性能</strong>:★★★★☆</li> <li><strong>手段</strong>:Introspector机制获取到类的属性来进行赋值操作</li> <li><strong>点评</strong>:同样是反射的属性拷贝,Spring 提供的 copyProperties 要比 Apache 好用的多,只要你不用错,基本不会有啥问题。</li> </ul> <h3 id="5-bean-mapping">5. Bean Mapping</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">BeanMappingAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="nc">UserDTO</span> <span class="n">userDTO</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserDTO</span><span class="o">();</span> <span class="nc">BeanUtil</span><span class="o">.</span><span class="na">copyProperties</span><span class="o">(</span><span class="kt">var</span><span class="o">,</span> <span class="n">userDTO</span><span class="o">);</span> <span class="k">return</span> <span class="n">userDTO</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>推荐</strong>:★★☆☆☆</li> <li><strong>性能</strong>:★★★☆☆</li> <li><strong>手段</strong>:属性拷贝</li> <li><strong>点评</strong>:性能一般</li> </ul> <h3 id="6-bean-mapping-asm">6. Bean Mapping ASM</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">BeanMappingAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="nc">UserDTO</span> <span class="n">userDTO</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserDTO</span><span class="o">();</span> <span class="nc">BeanUtil</span><span class="o">.</span><span class="na">copyProperties</span><span class="o">(</span><span class="kt">var</span><span class="o">,</span> <span class="n">userDTO</span><span class="o">);</span> <span class="k">return</span> <span class="n">userDTO</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>推荐</strong>:★★★☆☆</li> <li><strong>性能</strong>:★★★★☆</li> <li><strong>手段</strong>:基于ASM字节码框架实现</li> <li><strong>点评</strong>:与普通的 Bean Mapping 相比,性能有所提升,可以使用。</li> </ul> <h3 id="7-beancopier">7. BeanCopier</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">BeanCopierAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="nc">UserDTO</span> <span class="n">userDTO</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">UserDTO</span><span class="o">();</span> <span class="nc">BeanCopier</span> <span class="n">beanCopier</span> <span class="o">=</span> <span class="nc">BeanCopier</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="kt">var</span><span class="o">.</span><span class="na">getClass</span><span class="o">(),</span> <span class="n">userDTO</span><span class="o">.</span><span class="na">getClass</span><span class="o">(),</span> <span class="kc">false</span><span class="o">);</span> <span class="n">beanCopier</span><span class="o">.</span><span class="na">copy</span><span class="o">(</span><span class="kt">var</span><span class="o">,</span> <span class="n">userDTO</span><span class="o">,</span> <span class="kc">null</span><span class="o">);</span> <span class="k">return</span> <span class="n">userDTO</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>推荐</strong>:★★★☆☆</li> <li><strong>性能</strong>:★★★★☆</li> <li><strong>手段</strong>:基于CGlib字节码操作生成get、set方法</li> <li><strong>点评</strong>:整体性能很不错,使用也不复杂,可以使用</li> </ul> <h3 id="8-orika">8. Orika</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">OrikaAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="cm">/** * 构造一个MapperFactory */</span> <span class="kd">private</span> <span class="kd">static</span> <span class="nc">MapperFactory</span> <span class="n">mapperFactory</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultMapperFactory</span><span class="o">.</span><span class="na">Builder</span><span class="o">().</span><span class="na">build</span><span class="o">();</span> <span class="kd">static</span> <span class="o">{</span> <span class="n">mapperFactory</span><span class="o">.</span><span class="na">classMap</span><span class="o">(</span><span class="nc">UserDTO</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">UserVO</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="o">.</span><span class="na">field</span><span class="o">(</span><span class="s">"userId"</span><span class="o">,</span> <span class="s">"userId"</span><span class="o">)</span> <span class="c1">// 字段不一致时可以指定</span> <span class="o">.</span><span class="na">byDefault</span><span class="o">()</span> <span class="o">.</span><span class="na">register</span><span class="o">();</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mapperFactory</span><span class="o">.</span><span class="na">getMapperFacade</span><span class="o">().</span><span class="na">map</span><span class="o">(</span><span class="kt">var</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>官网</strong>:<a href="https://orika-mapper.github.io/orika-docs/">https://orika-mapper.github.io/orika-docs/</a></li> <li><strong>推荐</strong>:★★☆☆☆</li> <li><strong>性能</strong>:★★★☆☆</li> <li><strong>手段</strong>:基于字节码生成映射对象</li> <li><strong>点评</strong>:测试性能不是太突出,如果使用的话需要把 MapperFactory 的构建优化成 Bean 对象</li> </ul> <h3 id="9-dozer">9. Dozer</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">DozerAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">static</span> <span class="nc">DozerBeanMapper</span> <span class="n">mapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DozerBeanMapper</span><span class="o">();</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">mapper</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="kt">var</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>官网</strong>:<a href="http://dozer.sourceforge.net/documentation/gettingstarted.html">http://dozer.sourceforge.net/documentation/gettingstarted.html</a></li> <li><strong>推荐</strong>:★☆☆☆☆</li> <li><strong>性能</strong>:★★☆☆☆</li> <li><strong>手段</strong>:属性映射框架,递归的方式复制对象</li> <li><strong>点评</strong>:性能有点差,不建议使用</li> </ul> <h3 id="10-modelmapper">10. ModelMapper</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Component</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">ModelMapperAssembler</span> <span class="kd">implements</span> <span class="nc">IAssembler</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">static</span> <span class="nc">ModelMapper</span> <span class="n">modelMapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ModelMapper</span><span class="o">();</span> <span class="kd">static</span> <span class="o">{</span> <span class="n">modelMapper</span><span class="o">.</span><span class="na">addMappings</span><span class="o">(</span><span class="k">new</span> <span class="nc">PropertyMap</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;()</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">configure</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// 属性值不一样可以自己操作</span> <span class="n">map</span><span class="o">().</span><span class="na">setUserId</span><span class="o">(</span><span class="n">source</span><span class="o">.</span><span class="na">getUserId</span><span class="o">());</span> <span class="o">}</span> <span class="o">});</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="kt">var</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">modelMapper</span><span class="o">.</span><span class="na">map</span><span class="o">(</span><span class="kt">var</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>官网</strong>:<a href="http://modelmapper.org">http://modelmapper.org</a></li> <li><strong>推荐</strong>:★★★☆☆</li> <li><strong>性能</strong>:★★★☆☆</li> <li><strong>手段</strong>:基于ASM字节码实现</li> <li><strong>点评</strong>:转换对象数量较少时性能不错,如果同时大批量转换对象,性能有所下降</li> </ul> <h3 id="11-jmapper">11. JMapper</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">JMapper</span><span class="o">&lt;</span><span class="nc">UserDTO</span><span class="o">,</span> <span class="nc">UserVO</span><span class="o">&gt;</span> <span class="n">jMapper</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">JMapper</span><span class="o">&lt;&gt;(</span><span class="nc">UserDTO</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="nc">UserVO</span><span class="o">.</span><span class="na">class</span><span class="o">,</span> <span class="k">new</span> <span class="nc">JMapperAPI</span><span class="o">()</span> <span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="nc">JMapperAPI</span><span class="o">.</span><span class="na">mappedClass</span><span class="o">(</span><span class="nc">UserDTO</span><span class="o">.</span><span class="na">class</span><span class="o">)</span> <span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="nc">JMapperAPI</span><span class="o">.</span><span class="na">attribute</span><span class="o">(</span><span class="s">"userId"</span><span class="o">)</span> <span class="o">.</span><span class="na">value</span><span class="o">(</span><span class="s">"userId"</span><span class="o">))</span> <span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="nc">JMapperAPI</span><span class="o">.</span><span class="na">attribute</span><span class="o">(</span><span class="s">"userNickName"</span><span class="o">)</span> <span class="o">.</span><span class="na">value</span><span class="o">(</span><span class="s">"userNickName"</span><span class="o">))</span> <span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="nc">JMapperAPI</span><span class="o">.</span><span class="na">attribute</span><span class="o">(</span><span class="s">"createTime"</span><span class="o">)</span> <span class="o">.</span><span class="na">value</span><span class="o">(</span><span class="s">"createTime"</span><span class="o">))</span> <span class="o">));</span> </code></pre></div></div> <ul> <li><strong>官网</strong>:<a href="https://github.com/jmapper-framework/jmapper-core/wiki">https://github.com/jmapper-framework/jmapper-core/wiki</a></li> <li><strong>推荐</strong>:★★★★☆</li> <li><strong>性能</strong>:★★★★★</li> <li><strong>手段</strong>:Elegance, high performance and robustness all in one java bean mapper</li> <li><strong>点评</strong>:速度真心可以,不过结合 SpringBoot 感觉有的一点点麻烦,可能姿势不对</li> </ul> <h3 id="12-mapstruct">12. MapStruct</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Mapper</span><span class="o">(</span><span class="n">componentModel</span> <span class="o">=</span> <span class="s">"spring"</span><span class="o">,</span> <span class="n">unmappedTargetPolicy</span> <span class="o">=</span> <span class="nc">ReportingPolicy</span><span class="o">.</span><span class="na">IGNORE</span><span class="o">,</span> <span class="n">unmappedSourcePolicy</span> <span class="o">=</span> <span class="nc">ReportingPolicy</span><span class="o">.</span><span class="na">IGNORE</span><span class="o">)</span> <span class="kd">public</span> <span class="kd">interface</span> <span class="nc">UserDTOMapping</span> <span class="kd">extends</span> <span class="nc">IMapping</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="o">{</span> <span class="cm">/** 用于测试的单例 */</span> <span class="nc">IMapping</span><span class="o">&lt;</span><span class="nc">UserVO</span><span class="o">,</span> <span class="nc">UserDTO</span><span class="o">&gt;</span> <span class="no">INSTANCE</span> <span class="o">=</span> <span class="nc">Mappers</span><span class="o">.</span><span class="na">getMapper</span><span class="o">(</span><span class="nc">UserDTOMapping</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="nd">@Mapping</span><span class="o">(</span><span class="n">target</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">,</span> <span class="n">source</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">)</span> <span class="nd">@Mapping</span><span class="o">(</span><span class="n">target</span> <span class="o">=</span> <span class="s">"createTime"</span><span class="o">,</span> <span class="n">dateFormat</span> <span class="o">=</span> <span class="s">"yyyy-MM-dd HH:mm:ss"</span><span class="o">)</span> <span class="nd">@Override</span> <span class="nc">UserDTO</span> <span class="nf">sourceToTarget</span><span class="o">(</span><span class="nc">UserVO</span> <span class="n">var1</span><span class="o">);</span> <span class="nd">@Mapping</span><span class="o">(</span><span class="n">target</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">,</span> <span class="n">source</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">)</span> <span class="nd">@Mapping</span><span class="o">(</span><span class="n">target</span> <span class="o">=</span> <span class="s">"createTime"</span><span class="o">,</span> <span class="n">dateFormat</span> <span class="o">=</span> <span class="s">"yyyy-MM-dd HH:mm:ss"</span><span class="o">)</span> <span class="nd">@Override</span> <span class="nc">UserVO</span> <span class="nf">targetToSource</span><span class="o">(</span><span class="nc">UserDTO</span> <span class="n">var1</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>官网</strong>:<a href="https://github.com/mapstruct/mapstruct">https://github.com/mapstruct/mapstruct</a></li> <li><strong>推荐</strong>:★★★★★</li> <li><strong>性能</strong>:★★★★★</li> <li><strong>手段</strong>:直接在编译期生成对应的get、set,像手写的代码一样</li> <li><strong>点评</strong>:速度很快,不需要到运行期处理,结合到框架中使用方便</li> </ul> <h2 id="四总结">四、总结</h2> <ul> <li>其实对象属性转换的操作无非是基于反射、AOP、CGlib、ASM、Javassist 在编译时和运行期进行处理,再有好的思路就是在编译前生成出对应的get、set,就像手写出来的一样。</li> <li>所以我更推荐我喜欢的 MapStruct,这货用起来还是比较舒服的,一种是来自于功能上的拓展性,易用性和兼容性。</li> <li>无论哪种使用,都要做一下完整的测试和验证,不要上来就复制粘贴,否则你可能早早的就把挖好坑了,当然不一定是哪个兄弟来填坑了。</li> </ul> <h2 id="五系列推荐">五、系列推荐</h2> <ul> <li><a href="https://bugstack.cn/framework/2021/08/22/%E5%B8%A6%E5%A4%B4%E6%92%B8%E9%A1%B9%E7%9B%AE-DDD-+-RPC-%E5%BC%80%E5%8F%91%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84-%E6%8A%BD%E5%A5%96%E7%B3%BB%E7%BB%9F.html">DDD + RPC 开发分布式架构,抽奖系统</a></li> <li><a href="https://bugstack.cn/itstack-ark-middleware/2021/03/31/SpringBoot-%E4%B8%AD%E9%97%B4%E4%BB%B6%E8%AE%BE%E8%AE%A1%E5%92%8C%E5%BC%80%E5%8F%91-%E4%B8%93%E6%A0%8F%E5%B0%8F%E5%86%8C%E4%B8%8A%E7%BA%BF%E5%95%A6.html">SpringBoot 中间件设计和开发</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2021/02/24/%E5%8D%8A%E5%B9%B4%E7%AD%9B%E9%80%89%E4%BA%86400+%E4%BB%BD%E7%AE%80%E5%8E%86-%E5%91%8A%E8%AF%89%E4%BD%A0%E6%80%8E%E4%B9%88%E5%86%99%E4%BC%9A%E8%A2%AB%E6%92%A9.html">半年招聘筛选了400+份简历,告诉你怎么写容易被撩!</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2021/01/17/%E6%95%B0%E5%AD%A6-%E7%A6%BB%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%91%98%E6%9C%89%E5%A4%9A%E8%BF%91.html">数学,离一个程序员有多近?</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2020/08/25/13%E5%B9%B4%E6%AF%95%E4%B8%9A-%E7%94%A8%E4%B8%A4%E5%B9%B4%E6%97%B6%E9%97%B4%E4%BB%8E%E5%A4%96%E5%8C%85%E8%B5%B0%E8%BF%9B%E4%BA%92%E8%81%94%E7%BD%91%E5%A4%A7%E5%8E%82.html">13年毕业,用两年时间从外包走进互联网大厂</a></li> </ul> Sun, 10 Oct 2021 00:00:00 +0800 https://bugstack.cn/itstack-code-life/2021/10/10/12%E7%A7%8D-vo2dto-%E6%96%B9%E6%B3%95-%E5%B0%B1-BeanUtils.copyProperties-%E5%8E%8B%E6%B5%8B%E6%9C%80%E6%8B%89%E8%83%AF.html https://bugstack.cn/itstack-code-life/2021/10/10/12%E7%A7%8D-vo2dto-%E6%96%B9%E6%B3%95-%E5%B0%B1-BeanUtils.copyProperties-%E5%8E%8B%E6%B5%8B%E6%9C%80%E6%8B%89%E8%83%AF.html java itstack-code-life itstack-code-life p3c 插件,是怎么检查出你那屎山的代码? <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a> <br />原文:<a href="https://mp.weixin.qq.com/s/RwzprbY2AhdgslY8tbVL-A">https://mp.weixin.qq.com/s/RwzprbY2AhdgslY8tbVL-A</a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一前言">一、前言</h2> <p><code class="highlighter-rouge">你会对你用到都技术,好奇吗?</code></p> <p>虽然我们都被称为码农,也都是写着代码,但因为所处场景需求的不同,所以各类码农也都做着不一样都事情。</p> <p>有些人统一规范、有些人开发组件、有些人编写业务、有些人倒腾验证,但越是工作内容简单如CRUD一样的码农,用到别人提供好的东西却是越多。一会安装个插件、一会引入个Jar包、一会调别人个接口,而自己的工作就像是装配工,东拼拼西凑凑,就把产品需求写完了。</p> <p>坏了,这么干可能几年下来,也不会有什么技术上都突破。因为你对那些使用都技术不好奇,不想知道它们是怎么实现的。就像阿里的P3C插件,是怎么检查代码分析出来我写的拉胯的呢?</p> <h2 id="二p3c-插件是什么">二、P3C 插件是什么</h2> <p>P3C 是阿里开源代码库的插件工程名称,它以<a href="https://github.com/alibaba/p3c/blob/master/Java%E5%BC%80%E5%8F%91%E6%89%8B%E5%86%8C%EF%BC%88%E5%B5%A9%E5%B1%B1%E7%89%88%EF%BC%89.pdf">阿里巴巴Java开发手册</a>为标准,用于监测代码质量的 IDEA/Eclipse 插件。</p> <p><img src="https://bugstack.cn/assets/images/guide/guide-2-01.png" alt="" /></p> <ul> <li>源码:<a href="https://github.com/alibaba/p3c">https://github.com/alibaba/p3c</a></li> </ul> <p>插件安装完成后,就可以按照编程规约,静态分析代码中出现的代码:命名风格、常量定义、集合处理、并发处理、OOP、控制语句、注释、异常等各项潜在风险,同时会给出一些优化操作和实例。</p> <p><img src="https://bugstack.cn/assets/images/guide/guide-2-02.png" alt="" /></p> <ul> <li>在遵守开发手册标准并按照插件检查都情况下,还是可以非常好的统一编码标准和风格都,也能剔除掉一些潜在都风险。</li> <li>如果你是新手编程用户或者想写出标准都代码,那么非常建议你按照这样都插件来辅助自己做代码开发。当然如果你所在的公司也有相应都标准手册和插件,也可以按照后遵守它都约定的。</li> </ul> <h2 id="三p3c-插件源码">三、P3C 插件源码</h2> <p>在最开始使用这类代码检查都插件的时候,就非常好奇它是怎么发现我的屎山代码的,用了什么样都技术原理呢,如果我能分析下是不是也可以把这样都技术手段用到其他地方。</p> <p>在分析这样一个代码检查插件前,先思考要从 IDEA 插件都源码查起,看看它是什么个逻辑,之后分析具体是如何使用都。其实这与一些其他的框架性源码学习都是类似的,拿到官网都文档、GitHub 对应的源码,按照步骤进行构建、部署、测试、调试、分析,进而找到核心原理。</p> <p>P3C 以 IDEA 插件开发为例,主要涉及到插件部分和规约部分,因为是把规约检查的能力与插件技术结合,所以会涉及到一些 IDEA 开发的技术。另外 P3C 插件涉及到都技术语言不只是 Java 还有一部分 kotlin 它是一种在 Java 虚拟机上运行的静态类型编程语言。</p> <ul> <li>插件源码:<a href="https://github.com/alibaba/p3c/blob/master/idea-plugin">https://github.com/alibaba/p3c/blob/master/idea-plugin</a></li> <li>规约源码:<a href="https://github.com/alibaba/p3c/tree/master/p3c-pmd">https://github.com/alibaba/p3c/tree/master/p3c-pmd</a></li> </ul> <h3 id="1-插件配置-p3cxml">1. 插件配置 p3c.xml</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">action</span> <span class="kd">class</span><span class="err">="</span><span class="nc">com</span><span class="o">.</span><span class="na">alibaba</span><span class="o">.</span><span class="na">p3c</span><span class="o">.</span><span class="na">idea</span><span class="o">.</span><span class="na">action</span><span class="o">.</span><span class="na">AliInspectionAction</span><span class="s">" id="</span><span class="nc">AliP3CInspectionAction</span><span class="s">" popup="</span><span class="kc">true</span><span class="s">" text="</span><span class="n">编码规约扫描</span><span class="s">" icon="</span><span class="nc">P3cIcons</span><span class="o">.</span><span class="na">ANALYSIS_ACTION</span><span class="s">"&gt; &lt;keyboard-shortcut keymap="</span><span class="n">$default</span><span class="s">" first-keystroke="</span><span class="n">shift</span> <span class="n">ctrl</span> <span class="n">alt</span> <span class="no">J</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">MainToolBar</span><span class="s">" anchor="</span><span class="n">last</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">ProjectViewPopupMenu</span><span class="s">" anchor="</span><span class="n">last</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">ChangesViewPopupMenu</span><span class="s">" anchor="</span><span class="n">last</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">EditorPopupMenu</span><span class="s">" anchor="</span><span class="n">last</span><span class="err">"</span><span class="o">/&gt;</span> <span class="o">&lt;/</span><span class="n">action</span><span class="o">&gt;</span> </code></pre></div></div> <ul> <li>翻看源码最重要是要找到入口,这个入口通常也是你在使用插件、程序、接口等时候,最直接进入都那部分。</li> <li>那么我们在使用 P3C 插件的时候,最明显的就是 <code class="highlighter-rouge">编码规约扫描</code> 通过源码中找到这个关键字,看它都涉及了哪个类都配置。</li> <li>action 是 IDEA 插件中用于配置窗体事件入口都地方,以及把这个操作配置到哪个按钮下和对应都快捷键。</li> </ul> <h3 id="2-编码规约扫描-aliinspectionaction">2. 编码规约扫描( AliInspectionAction)</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">class</span> <span class="nc">AliInspectionAction</span> <span class="o">:</span> <span class="nc">AnAction</span><span class="o">()</span> <span class="o">{</span> <span class="n">override</span> <span class="n">fun</span> <span class="nf">actionPerformed</span><span class="o">(</span><span class="nl">e:</span> <span class="nc">AnActionEvent</span><span class="o">)</span> <span class="o">{</span> <span class="n">val</span> <span class="n">project</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">project</span> <span class="o">?:</span> <span class="k">return</span> <span class="n">val</span> <span class="n">analysisUIOptions</span> <span class="o">=</span> <span class="nc">ServiceManager</span><span class="o">.</span><span class="na">getService</span><span class="o">(</span><span class="n">project</span><span class="o">,</span> <span class="nl">AnalysisUIOptions:</span><span class="o">:</span><span class="kd">class</span><span class="err">.</span><span class="nc">java</span><span class="o">)!!</span> <span class="n">analysisUIOptions</span><span class="o">.</span><span class="na">GROUP_BY_SEVERITY</span> <span class="o">=</span> <span class="kc">true</span> <span class="n">val</span> <span class="n">managerEx</span> <span class="o">=</span> <span class="nc">InspectionManager</span><span class="o">.</span><span class="na">getInstance</span><span class="o">(</span><span class="n">project</span><span class="o">)</span> <span class="n">as</span> <span class="nc">InspectionManagerEx</span> <span class="n">val</span> <span class="n">toolWrappers</span> <span class="o">=</span> <span class="nc">Inspections</span><span class="o">.</span><span class="na">aliInspections</span><span class="o">(</span><span class="n">project</span><span class="o">)</span> <span class="o">{</span> <span class="n">it</span><span class="o">.</span><span class="na">tool</span> <span class="n">is</span> <span class="nc">AliBaseInspection</span> <span class="o">}</span> <span class="n">val</span> <span class="n">psiElement</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">&lt;</span><span class="nc">PsiElement</span><span class="o">&gt;(</span><span class="nc">CommonDataKeys</span><span class="o">.</span><span class="na">PSI_ELEMENT</span><span class="o">)</span> <span class="n">val</span> <span class="n">psiFile</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">&lt;</span><span class="nc">PsiFile</span><span class="o">&gt;(</span><span class="nc">CommonDataKeys</span><span class="o">.</span><span class="na">PSI_FILE</span><span class="o">)</span> <span class="n">val</span> <span class="n">virtualFile</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">&lt;</span><span class="nc">VirtualFile</span><span class="o">&gt;(</span><span class="nc">CommonDataKeys</span><span class="o">.</span><span class="na">VIRTUAL_FILE</span><span class="o">)</span> <span class="o">...</span> <span class="n">createContext</span><span class="o">(</span> <span class="n">toolWrappers</span><span class="o">,</span> <span class="n">managerEx</span><span class="o">,</span> <span class="n">element</span><span class="o">,</span> <span class="n">projectDir</span><span class="o">,</span> <span class="n">analysisScope</span> <span class="o">).</span><span class="na">doInspections</span><span class="o">(</span><span class="n">analysisScope</span><span class="o">)</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>这是一个基于 kotlin 语言开发的插件代码逻辑,它通过 actionPerformed 方法获取到工程信息、类信息等,接下来就可以执行代码检查了 doInspections</li> </ul> <h3 id="3-规约-p3c-pmd">3. 规约 p3c-pmd</h3> <p>当我们再往下翻看阅读的时候,就看到了一个关于 pmd 的东西。PMD 是一款采用 BSD 协议发布的Java 程序静态代码检查工具,当使用PMD规则分析Java源码时,PMD首先利用JavaCC和EBNF文法产生了一个语法分析器,用来分析普通文本形式的Java代码,产生符合特定语法结构的语法,同时又在JavaCC的基础上添加了语义的概念即JJTree,通过JJTree的一次转换,这样就将Java代码转换成了一个AST,AST是Java符号流之上的语义层,PMD把AST处理成一个符号表。然后编写PMD规则,一个PMD规则可以看成是一个Visitor,通过遍历AST找出多个对象之间的一种特定模式,即代码所存在的问题。该软件功能强大,扫描效率高,是 Java 程序员 debug 的好帮手。</p> <p>那么 p3c-pmd 是什么呢?</p> <p><img src="https://bugstack.cn/assets/images/guide/guide-2-03.png" alt="" /></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">ViolationUtils</span><span class="o">.</span><span class="na">addViolationWithPrecisePosition</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">node</span><span class="o">,</span> <span class="n">data</span><span class="o">,</span> <span class="nc">I18nResources</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(</span><span class="s">"java.naming.ClassNamingShouldBeCamelRule.violation.msg"</span><span class="o">,</span> <span class="n">node</span><span class="o">.</span><span class="na">getImage</span><span class="o">()));</span> </code></pre></div></div> <ul> <li>p3c-pmd 插件是基于 PMD 实现的,更具体的来说是基于 pmd-java 的,因为 PMD 不仅支持 Java 代码分析,还支持其他多种语言。</li> <li>具体自定义规则的方式,通过自定义Java类和XPATH规则实现。</li> </ul> <h2 id="四规约监测案例">四、规约监测案例</h2> <p>讲道理,说一千道一万,还得是拿出代码跑一下,才知道 PMD 具体是什么个样子。</p> <h3 id="1-测试工程">1. 测试工程</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">guide</span><span class="o">-</span><span class="n">pmd</span> <span class="err">└──</span> <span class="n">src</span> <span class="err">├──</span> <span class="n">main</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">cn</span><span class="o">.</span><span class="na">itedus</span><span class="o">.</span><span class="na">guide</span><span class="o">.</span><span class="na">pmd</span><span class="o">.</span><span class="na">rule</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">naming</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">ClassNamingShouldBeCamelRule</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">ConstantFieldShouldBeUpperCaseRule</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="nc">LowerCamelCaseVariableNamingRule</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">utils</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">StringAndCharConstants</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="nc">ViolationUtils</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="nc">I18nResources</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">resources</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">rule</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">ali</span><span class="o">-</span><span class="n">naming</span><span class="o">.</span><span class="na">xml</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">messages</span><span class="o">.</span><span class="na">xml</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">namelist</span><span class="o">.</span><span class="na">properties</span> <span class="err">└──</span> <span class="n">test</span> <span class="err">└──</span> <span class="n">java</span> <span class="err">└──</span> <span class="n">cn</span><span class="o">.</span><span class="na">itedus</span><span class="o">.</span><span class="na">demo</span><span class="o">.</span><span class="na">test</span> <span class="err">├──</span> <span class="nc">ApiTest</span><span class="o">.</span><span class="na">java</span> <span class="err">└──</span> <span class="nc">TErrDto</span><span class="o">.</span><span class="na">java</span> </code></pre></div></div> <ul> <li><strong>源码</strong>:<a href="https://github.com/fuzhengwei/guide-pmd">https://github.com/fuzhengwei/guide-pmd</a></li> </ul> <p>这是一个类似 p3c-pmd 的测试工程,通过自行扩展重写代码监测规约的方式,来处理自己关于代码的审核标准处理。</p> <ul> <li>naming 下的类是用于处理一些和名称相关的规则,类名、属性名、方法名等</li> <li>resources 下 ali-naming.xml 是规约的配置文件</li> </ul> <h3 id="2-驼峰命名规约">2. 驼峰命名规约</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ClassNamingShouldBeCamelRule</span> <span class="kd">extends</span> <span class="nc">AbstractJavaRule</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Pattern</span> <span class="no">PATTERN</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"^I?([A-Z][a-z0-9]+)+(([A-Z])|(DO|DTO|VO|DAO|BO|DAOImpl|YunOS|AO|PO))?$"</span><span class="o">);</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">visit</span><span class="o">(</span><span class="nc">ASTClassOrInterfaceDeclaration</span> <span class="n">node</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span> <span class="k">if</span> <span class="o">(</span><span class="no">PATTERN</span><span class="o">.</span><span class="na">matcher</span><span class="o">(</span><span class="n">node</span><span class="o">.</span><span class="na">getImage</span><span class="o">()).</span><span class="na">matches</span><span class="o">())</span> <span class="o">{</span> <span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">visit</span><span class="o">(</span><span class="n">node</span><span class="o">,</span> <span class="n">data</span><span class="o">);</span> <span class="o">}</span> <span class="nc">ViolationUtils</span><span class="o">.</span><span class="na">addViolationWithPrecisePosition</span><span class="o">(</span><span class="k">this</span><span class="o">,</span> <span class="n">node</span><span class="o">,</span> <span class="n">data</span><span class="o">,</span> <span class="nc">I18nResources</span><span class="o">.</span><span class="na">getMessage</span><span class="o">(</span><span class="s">"java.naming.ClassNamingShouldBeCamelRule.violation.msg"</span><span class="o">,</span> <span class="n">node</span><span class="o">.</span><span class="na">getImage</span><span class="o">()));</span> <span class="k">return</span> <span class="kd">super</span><span class="o">.</span><span class="na">visit</span><span class="o">(</span><span class="n">node</span><span class="o">,</span> <span class="n">data</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>通过继承 PMD 提供的 AbstractJavaRule 抽象类,重写 visit 方法,使用正则的方式进行验证。</li> <li>visit 方法都入参类型非常多,分别用于处理类、接口、方法、代码等各项内容的监测处理,只要重写需要的方法,在里面进行自己都处理就可以。</li> <li>ClassNamingShouldBeCamelRule、ConstantFieldShouldBeUpperCaseRule、LowerCamelCaseVariableNamingRule 三个类都功能类似,这里就不一一展示了,可以直接参考源码。</li> </ul> <h3 id="3-ali-namingxml-配置">3. ali-naming.xml 配置</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">rule</span> <span class="n">name</span><span class="o">=</span><span class="s">"ClassNamingShouldBeCamelRule"</span> <span class="n">language</span><span class="o">=</span><span class="s">"java"</span> <span class="n">since</span><span class="o">=</span><span class="s">"1.6"</span> <span class="n">message</span><span class="o">=</span><span class="s">"java.naming.ClassNamingShouldBeCamelRule.rule.msg"</span> <span class="kd">class</span><span class="err">="</span><span class="nc">cn</span><span class="o">.</span><span class="na">itedus</span><span class="o">.</span><span class="na">guide</span><span class="o">.</span><span class="na">pmd</span><span class="o">.</span><span class="na">rule</span><span class="o">.</span><span class="na">naming</span><span class="o">.</span><span class="na">ClassNamingShouldBeCamelRule</span><span class="err">"</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="n">priority</span><span class="o">&gt;</span><span class="mi">3</span><span class="o">&lt;/</span><span class="n">priority</span><span class="o">&gt;</span> <span class="o">&lt;/</span><span class="n">rule</span><span class="o">&gt;</span> </code></pre></div></div> <ul> <li>在 ali-naming.xml 用于配置规约处理类、priority 级别、message 提醒文字。</li> <li>同时还可以配置代码示例,使用 <code class="highlighter-rouge">&lt;example&gt;</code> 标签,在里面写好标准代码即可。</li> </ul> <h3 id="4-测试验证规约">4. 测试验证规约</h3> <p><strong>问题类示例</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">TErrDto</span> <span class="o">{</span> <span class="kd">public</span> <span class="kd">static</span> <span class="kd">final</span> <span class="nc">Long</span> <span class="n">max</span> <span class="o">=</span> <span class="mi">50000L</span><span class="o">;</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">QueryUserInfo</span><span class="o">(){</span> <span class="kt">boolean</span> <span class="n">baz</span> <span class="o">=</span> <span class="kc">true</span><span class="o">;</span> <span class="k">while</span> <span class="o">(</span><span class="n">baz</span><span class="o">)</span> <span class="n">baz</span> <span class="o">=</span> <span class="kc">false</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><strong>单元测试</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test_naming</span><span class="o">(){</span> <span class="nc">String</span><span class="o">[]</span> <span class="n">str</span> <span class="o">=</span> <span class="o">{</span> <span class="s">"-d"</span><span class="o">,</span> <span class="s">"E:\\itstack\\git\\github.com\\guide-pmd\\src\\test\\java\\cn\\itedus\\demo\\test\\TErrDto.java"</span><span class="o">,</span> <span class="s">"-f"</span><span class="o">,</span> <span class="s">"text"</span><span class="o">,</span> <span class="s">"-R"</span><span class="o">,</span> <span class="s">"E:\\itstack\\git\\github.com\\guide-pmd\\src\\main\\resources\\rule\\ali-naming.xml"</span> <span class="c1">// "category/java/codestyle.xml"</span> <span class="o">};</span> <span class="no">PMD</span><span class="o">.</span><span class="na">main</span><span class="o">(</span><span class="n">str</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>规约的测试验证可以直接使用 PMD.main 方法,在方法中提供字符串数组入参,这里的代码监测地址和规约配置需要是绝对路径。</li> </ul> <p><strong>测试结果</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">TErrDto</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">3</span><span class="o">:</span> <span class="err">【</span><span class="nc">TErrDto</span><span class="err">】</span><span class="n">不符合UpperCamelCase命名风格</span> <span class="nc">TErrDto</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">5</span><span class="o">:</span> <span class="n">常量</span><span class="err">【</span><span class="n">max</span><span class="err">】</span><span class="n">命名应全部大写并以下划线分隔</span> <span class="nc">TErrDto</span><span class="o">.</span><span class="na">java</span><span class="o">:</span><span class="mi">7</span><span class="o">:</span> <span class="n">方法名</span><span class="err">【</span><span class="nc">QueryUserInfo</span><span class="err">】</span><span class="n">不符合lowerCamelCase命名风格</span> <span class="nc">Process</span> <span class="n">finished</span> <span class="n">with</span> <span class="n">exit</span> <span class="n">code</span> <span class="mi">4</span> </code></pre></div></div> <ul> <li>从测试结果可以看到,我们写的三个代码规约分别监测出了代码的命名风格、常量大写、方法名不符合驼峰标识。</li> <li>同时你还可以测试 <code class="highlighter-rouge">category/java/codestyle.xml</code> 这个是 PMD 自身提供好的规约监测。</li> </ul> <h2 id="五扩展了解-sonar">五、扩展了解 Sonar</h2> <p>其实有了 PMD 静态代码检查规约,能做都事情就很多,不是只对正在写的代码进行检查,还可以对不同阶段的代码进行分析和风险提醒,比如:准备提测阶段、已经上线完成,都可以做相应的监测处理。</p> <p>而 Sonar 就是一个这样都工具,它是一个Web系统,可以展现静态代码扫描的结果,结果是可以自定义的,支持多种语言的原理是它的扩展性。<a href="https://www.sonarqube.org/">https://www.sonarqube.org/</a></p> <p><img src="https://bugstack.cn/assets/images/guide/guide-2-04.png" alt="" /></p> <ul> <li>不遵循代码标准:sonar可以通过PMD,CheckStyle,Findbugs等等代码规则检测工具规范代码编写。</li> <li>潜在的缺陷:sonar可以通过PMD,CheckStyle,Findbugs等等代码规则检测工具检 测出潜在的缺陷。</li> <li>糟糕的复杂度分布:文件、类、方法等,如果复杂度过高将难以改变,这会使得开发人员 难以理解它们, 且如果没有自动化的单元测试,对于程序中的任何组件的改变都将可能导致需要全面的回归测试。</li> <li>重复:显然程序中包含大量复制粘贴的代码是质量低下的,sonar可以展示 源码中重复严重的地方。</li> <li>注释不足或者过多:没有注释将使代码可读性变差,特别是当不可避免地出现人员变动 时,程序的可读性将大幅下降 而过多的注释又会使得开发人员将精力过多地花费在阅读注释上,亦违背初衷。</li> <li>缺乏单元测试:sonar可以很方便地统计并展示单元测试覆盖率。</li> <li>糟糕的设计:通过sonar可以找出循环,展示包与包、类与类之间的相互依赖关系,可以检测自定义的架构规则 通过sonar可以管理第三方的jar包,可以利用LCOM4检测单个任务规则的应用情况, 检测耦合。</li> <li>提高代码质量:了解自己在编码过程中犯过的错误,让自己的代码更具有可读性和维护性。</li> </ul> <h2 id="六总结">六、总结</h2> <ul> <li>PMD 是一款采用 BSD 协议的代码检查工具,你可以扩展实现为自己的标准和规范以及完善个性的提醒和修复操作。</li> <li>另外基于 IDEA 插件实现的代码检查或者有审计要求的处理,也可以基于 IDEA 插件做更多的扩展,比如提醒修复、提供修复操作、自身业务逻辑的检查。例如momo开源库下的一款IDEA静态代码安全审计及漏洞一键修复插件 <a href="https://github.com/momosecurity/momo-code-sec-inspector-java">https://github.com/momosecurity/momo-code-sec-inspector-java</a></li> <li>这里补充一点,kotlin 语言可以在 IDEA 中转换为 Java 语言,这样你在阅读类似这样的代码时候,如果不好看懂也可以转换一下在阅读。此外 IDEA 插件开发需要基于 Gradle 或者本身提供都模版进行创建,如果感兴趣也可以阅读我写的 IDEA 插件开发文章。</li> </ul> <h2 id="七系列推荐">七、系列推荐</h2> <ul> <li><a href="https://bugstack.cn/framework/2021/02/04/%E5%9F%BA%E4%BA%8EIDEA%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%92%8C%E5%AD%97%E8%8A%82%E7%A0%81%E6%8F%92%E6%A1%A9%E6%8A%80%E6%9C%AF-%E5%AE%9E%E7%8E%B0%E7%A0%94%E5%8F%91%E4%BA%A4%E4%BB%98%E8%B4%A8%E9%87%8F%E8%87%AA%E5%8A%A8%E5%88%86%E6%9E%90.html">基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析</a></li> <li><a href="https://bugstack.cn/itstack-ark-middleware/2021/08/27/%E6%8A%80%E6%9C%AF%E8%B0%83%E7%A0%94-IDEA-%E6%8F%92%E4%BB%B6%E6%80%8E%E4%B9%88%E5%BC%80%E5%8F%91.html">技术调研,IDEA 插件怎么开发「脚手架、低代码可视化编排、接口生成测试」?</a></li> <li><a href="https://bugstack.cn/itstack-ark-middleware/2021/03/31/SpringBoot-%E4%B8%AD%E9%97%B4%E4%BB%B6%E8%AE%BE%E8%AE%A1%E5%92%8C%E5%BC%80%E5%8F%91-%E4%B8%93%E6%A0%8F%E5%B0%8F%E5%86%8C%E4%B8%8A%E7%BA%BF%E5%95%A6.html">《SpringBoot 中间件设计和开发》</a></li> <li><a href="https://bugstack.cn/framework/2021/03/04/%E7%AC%94%E8%AE%B0%E6%95%B4%E7%90%86-%E6%8A%80%E6%9C%AF%E6%9E%B6%E6%9E%84%E6%B6%B5%E7%9B%96%E5%86%85%E5%AE%B9%E5%92%8C%E6%BC%94%E5%8F%98%E8%BF%87%E7%A8%8B%E6%80%BB%E7%BB%93.html">笔记整理:技术架构涵盖内容和演变过程总结</a></li> <li><a href="https://bugstack.cn/itstack-demo-netty-3/2021/08/17/%E7%BB%99%E5%AD%A6%E4%B9%A0%E5%8A%A0%E7%82%B9%E5%AE%9E%E8%B7%B5-%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E5%88%86%E5%B8%83%E5%BC%8FIM(%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1)%E7%B3%BB%E7%BB%9F.html">给学习加点实践,开发一个分布式IM(即时通信)系统!</a></li> </ul> Mon, 27 Sep 2021 00:00:00 +0800 https://bugstack.cn/itstack-demo-any/2021/09/27/p3c-%E6%8F%92%E4%BB%B6-%E6%98%AF%E6%80%8E%E4%B9%88%E6%A3%80%E6%9F%A5%E5%87%BA%E4%BD%A0%E9%82%A3%E5%B1%8E%E5%B1%B1%E7%9A%84%E4%BB%A3%E7%A0%81.html https://bugstack.cn/itstack-demo-any/2021/09/27/p3c-%E6%8F%92%E4%BB%B6-%E6%98%AF%E6%80%8E%E4%B9%88%E6%A3%80%E6%9F%A5%E5%87%BA%E4%BD%A0%E9%82%A3%E5%B1%8E%E5%B1%B1%E7%9A%84%E4%BB%A3%E7%A0%81.html java itstack-code-life itstack-demo-any 还重构?就你那代码只能铲了重写! <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a> <br />原文:<a href="https://mp.weixin.qq.com/s/4xzd6mC2wKhXldrX50uk2w">https://mp.weixin.qq.com/s/4xzd6mC2wKhXldrX50uk2w</a></p> <h2 id="一前言">一、前言</h2> <p><code class="highlighter-rouge">我们不一样,就你没对象!</code> <em>对,你是面向过程编程的!</em></p> <p>我说的,绝大多数码农没日没夜被需求憋着肝出来的代码,无论有多么的吭哧瘪肚,都不可能有重构,只有重新写。为什么?因为重新写所花的时间成本,远比重构一份已经烂成团的代码,要节省时间。但谁又不敢保证重写完的代码,就比之前能好多少,况且还要承担着重写后的代码事故风险和几乎体现不出来的<code class="highlighter-rouge">业务价值</code>!</p> <p>虽然代码是给机器运行的,但同样也是给人看的,并且随着每次需求的迭代、变更、升级,都需要研发人员对同一份代码进行多次开发和上线,那么这里就会涉及到<code class="highlighter-rouge">可维护</code>、<code class="highlighter-rouge">易扩展</code>、<code class="highlighter-rouge">好交接</code>的特点。</p> <p>而那些不合理分层实现代码逻辑、不写代码注释、不按规范提交、不做格式化、命名随意甚至把 queryBatch 写成 queryBitch 的,都会造成后续代码没法重构的问题。那么接下来我们就分别介绍下,开发好能重构的代码,都要怎么干!</p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-00.png" alt="" /></p> <h2 id="二代码优化">二、代码优化</h2> <h3 id="1-约定规范">1. 约定规范</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="err">#</span> <span class="n">提交</span><span class="err">:</span><span class="n">主要</span> <span class="n">type</span> <span class="nl">feat:</span> <span class="n">增加新功能</span> <span class="nl">fix:</span> <span class="n">修复bug</span> <span class="err">#</span> <span class="n">提交</span><span class="err">:</span><span class="n">特殊</span> <span class="n">type</span> <span class="nl">docs:</span> <span class="n">只改动了文档相关的内容</span> <span class="nl">style:</span> <span class="n">不影响代码含义的改动</span><span class="err">,</span><span class="n">例如去掉空格</span><span class="err">、</span><span class="n">改变缩进</span><span class="err">、</span><span class="n">增删分号</span> <span class="nl">build:</span> <span class="n">构造工具的或者外部依赖的改动</span><span class="err">,</span><span class="n">例如webpack</span><span class="err">,</span><span class="n">npm</span> <span class="nl">refactor:</span> <span class="n">代码重构时使用</span> <span class="nl">revert:</span> <span class="n">执行git</span> <span class="n">revert打印的message</span> <span class="err">#</span> <span class="n">提交</span><span class="err">:</span><span class="n">暂不使用type</span> <span class="nl">test:</span> <span class="n">添加测试或者修改现有测试</span> <span class="nl">perf:</span> <span class="n">提高性能的改动</span> <span class="nl">ci:</span> <span class="n">与CI</span><span class="err">(</span><span class="n">持续集成服务</span><span class="err">)</span><span class="n">有关的改动</span> <span class="nl">chore:</span> <span class="n">不修改src或者test的其余修改</span><span class="err">,</span><span class="n">例如构建过程或辅助工具的变动</span> <span class="err">#</span> <span class="n">注释</span><span class="err">:</span><span class="n">类注释配置</span> <span class="cm">/** * @description: * @author: ${USER} * @date: ${DATE} */</span> </code></pre></div></div> <ul> <li><strong>分支</strong>:开发前提前约定好拉分支的规范,比如<code class="highlighter-rouge">日期_用户_用途</code>,210905_xfg_updateRuleLogic</li> <li><strong>提交</strong>:<code class="highlighter-rouge">作者,type: desc</code> 如:<code class="highlighter-rouge">小傅哥,fix:更新规则逻辑问题</code> <em>参考Commit message 规范</em></li> <li><strong>注释</strong>:包括类注释、方法注释、属性注释,在 IDEA 中可以设置类注释的头信息 <code class="highlighter-rouge">Editor -&gt; File and Code Templates -&gt; File Header </code> 推荐下载安装 IDEA P3C 插件 <code class="highlighter-rouge">Alibaba Java Coding Guidelines</code>,统一标准化编码方式。</li> </ul> <h3 id="2-接口标准">2. 接口标准</h3> <p>在编写 RPC 接口的时候,返回的结果中一定要包含明确的<code class="highlighter-rouge">Code码</code>和<code class="highlighter-rouge">Info描述</code>,否则使用方很难知道这个接口是否调用成功还是异常,以及是什么情况的异常。</p> <p><strong>定义 Result</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Result</span> <span class="kd">implements</span> <span class="n">java</span><span class="o">.</span><span class="na">io</span><span class="o">.</span><span class="na">Serializable</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">static</span> <span class="kd">final</span> <span class="kt">long</span> <span class="n">serialVersionUID</span> <span class="o">=</span> <span class="mi">752386055478765987L</span><span class="o">;</span> <span class="cm">/** 返回结果码 */</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">code</span><span class="o">;</span> <span class="cm">/** 返回结果信息 */</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">info</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">Result</span><span class="o">()</span> <span class="o">{</span> <span class="o">}</span> <span class="kd">public</span> <span class="nf">Result</span><span class="o">(</span><span class="nc">String</span> <span class="n">code</span><span class="o">,</span> <span class="nc">String</span> <span class="n">info</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">code</span> <span class="o">=</span> <span class="n">code</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">info</span> <span class="o">=</span> <span class="n">info</span><span class="o">;</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Result</span> <span class="nf">buildSuccessResult</span><span class="o">()</span> <span class="o">{</span> <span class="nc">Result</span> <span class="n">result</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Result</span><span class="o">();</span> <span class="n">result</span><span class="o">.</span><span class="na">setCode</span><span class="o">(</span><span class="nc">Constants</span><span class="o">.</span><span class="na">ResponseCode</span><span class="o">.</span><span class="na">SUCCESS</span><span class="o">.</span><span class="na">getCode</span><span class="o">());</span> <span class="n">result</span><span class="o">.</span><span class="na">setInfo</span><span class="o">(</span><span class="nc">Constants</span><span class="o">.</span><span class="na">ResponseCode</span><span class="o">.</span><span class="na">SUCCESS</span><span class="o">.</span><span class="na">getInfo</span><span class="o">());</span> <span class="k">return</span> <span class="n">result</span><span class="o">;</span> <span class="o">}</span> <span class="c1">// ...get/set</span> <span class="o">}</span> </code></pre></div></div> <p><strong>返回结果包装:继承</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">RuleResult</span> <span class="kd">extends</span> <span class="nc">Result</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">ruleId</span><span class="o">;</span> <span class="kd">private</span> <span class="nc">String</span> <span class="n">ruleDesc</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">RuleResult</span><span class="o">(</span><span class="nc">String</span> <span class="n">code</span><span class="o">,</span> <span class="nc">String</span> <span class="n">info</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">(</span><span class="n">code</span><span class="o">,</span> <span class="n">info</span><span class="o">);</span> <span class="o">}</span> <span class="c1">// ...get/set</span> <span class="o">}</span> <span class="c1">// 使用</span> <span class="kd">public</span> <span class="nc">RuleResult</span> <span class="nf">execRule</span><span class="o">(</span><span class="nc">DecisionMatter</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nf">RuleResult</span><span class="o">(</span><span class="nc">Constants</span><span class="o">.</span><span class="na">ResponseCode</span><span class="o">.</span><span class="na">SUCCESS</span><span class="o">.</span><span class="na">getCode</span><span class="o">(),</span> <span class="nc">Constants</span><span class="o">.</span><span class="na">ResponseCode</span><span class="o">.</span><span class="na">SUCCESS</span><span class="o">.</span><span class="na">getInfo</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <p><strong>返回结果包装:泛型</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">ResultData</span><span class="o">&lt;</span><span class="no">T</span><span class="o">&gt;</span> <span class="kd">implements</span> <span class="nc">Serializable</span> <span class="o">{</span> <span class="kd">private</span> <span class="nc">Result</span> <span class="n">result</span><span class="o">;</span> <span class="kd">private</span> <span class="no">T</span> <span class="n">data</span><span class="o">;</span> <span class="kd">public</span> <span class="nf">ResultData</span><span class="o">(</span><span class="nc">Result</span> <span class="n">result</span><span class="o">,</span> <span class="no">T</span> <span class="n">data</span><span class="o">)</span> <span class="o">{</span> <span class="k">this</span><span class="o">.</span><span class="na">result</span> <span class="o">=</span> <span class="n">result</span><span class="o">;</span> <span class="k">this</span><span class="o">.</span><span class="na">data</span> <span class="o">=</span> <span class="n">data</span><span class="o">;</span> <span class="o">}</span> <span class="c1">// ...get/set</span> <span class="o">}</span> <span class="c1">// 使用</span> <span class="kd">public</span> <span class="nc">ResultData</span><span class="o">&lt;</span><span class="nc">Rule</span><span class="o">&gt;</span> <span class="nf">execRule</span><span class="o">(</span><span class="nc">DecisionMatter</span> <span class="n">request</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="k">new</span> <span class="nc">ResultData</span><span class="o">&lt;</span><span class="nc">Rule</span><span class="o">&gt;(</span><span class="nc">Result</span><span class="o">.</span><span class="na">buildSuccessResult</span><span class="o">(),</span> <span class="k">new</span> <span class="nc">Rule</span><span class="o">());</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>两种接口返回结果的包装定义,都可以规范返回结果。在这样的方式包装后,使用方就可以用统一的方式来判断<code class="highlighter-rouge">Code码</code>并做出相应的处理。</li> </ul> <h3 id="3-库表设计">3. 库表设计</h3> <p><strong>三范式</strong>:是数据库的规范化的内容,所谓的数据库三范式通俗的讲就是设计数据库表所应该遵守的一套规范,如果不遵守就会造成设计的数据库不规范,出现数据库字段冗余,数据的查询,插入等操作等问题。</p> <p>数据库不仅仅只有三范式(1NF/2NF/3NF),还有BCNF、4NF、5NF…,不过在实际的数据库设计时,遵守前三个范式就足够了。再向下就会造成设计的数据库产生过多不必要的约束。</p> <h4 id="0nf">0NF</h4> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-01.png" alt="" /></p> <ul> <li>第零范式是指没有使用任何范式,数据存放冗余大量表字段,而且这样的表结构非常难以维护。</li> </ul> <h4 id="1nf">1NF</h4> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-02.png" alt="" /></p> <ul> <li>第一范式是在第零范式冗余字段上的改进,把重复字段抽离出来,设计成一个冗余数据较少便于存储和读取的表结构。</li> <li>同时在第一范式中也指出,表中的所有字段都应该是原子的、不可再分割的,例如:你不能把公司雇员表的部门名称和职责存放到一个字段。需要确保每列保持原子性</li> </ul> <h4 id="2nf">2NF</h4> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-03.png" alt="" /></p> <ul> <li>满足1NF后,要求表中的列,都必须依赖主键,确保每个列都和主键列之间联系,而不能间接联系,也就是一个表只能描述一件事情。需要确保表中的每列都和主键相关。</li> </ul> <h4 id="3nf">3NF</h4> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-04.png" alt="" /></p> <ul> <li>不能存在依赖关系,学号、姓名,到院系,院系到宿舍,需要确保每列都和主键列直接相关,而不是间接相关。</li> </ul> <h4 id="反三范式">反三范式</h4> <p>三大范式是设计数据库表结构的规则约束,但是在实际开发中允许局部变通:</p> <ol> <li>有时候为了便于查询,会在如订单表冗余上当时用户的快照信息,比如用户下单时候的一些设置信息。</li> <li>单列列表数据汇总到总表中一个数量值,便于查询的时候可以避免列表汇总操作。</li> <li>可以在设计表的时候冗余一些字段,避免因业务发展情况多变,考虑不周导致该表繁琐的问题。</li> </ol> <h3 id="4-算法逻辑">4. 算法逻辑</h3> <p>通常在我们实际的业务功能逻辑开发中,为了能满足一些高并发的场景,是不可能对数据库表上锁扣减库存、也不能直接for循环大量轮训操作的,通常需要考虑🤔在这样场景怎么去中心化以及降低时间复杂度。</p> <p><strong>秒杀:去中心化</strong></p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-05.png" alt="" /></p> <ul> <li><strong>背景</strong>:这个一个商品活动秒杀的实现方案,最开始的设计是基于一个活动号ID进行锁定,秒杀时锁定这个ID,用户购买完后就进行释放。但在大量用户抢购时,出现了秒杀分布式<code class="highlighter-rouge">独占锁</code>后的业务逻辑处理中发生异常,释放锁失败。导致所有的用户都不能再拿到锁,也就造成了有商品但不能下单的问题。</li> <li><strong>优化</strong>:优化独占竞态为分段静态,将活动ID+库存编号作为动态锁标识。当前秒杀的用户如果发生锁失败那么后面的用户可以继续秒杀不受影响。而失败的锁会有worker进行补偿恢复,那么最终会避免超卖以及不能售卖。</li> </ul> <p><strong>算法:反面教材</strong></p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-06.png" alt="" /></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test_idx_hashMap</span><span class="o">()</span> <span class="o">{</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">map</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;(</span><span class="mi">64</span><span class="o">);</span> <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"alderney"</span><span class="o">,</span> <span class="s">"未实现服务"</span><span class="o">);</span> <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"luminance"</span><span class="o">,</span> <span class="s">"未实现服务"</span><span class="o">);</span> <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"chorology"</span><span class="o">,</span> <span class="s">"未实现服务"</span><span class="o">);</span> <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"carline"</span><span class="o">,</span> <span class="s">"未实现服务"</span><span class="o">);</span> <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"fluorosis"</span><span class="o">,</span> <span class="s">"未实现服务"</span><span class="o">);</span> <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"angora"</span><span class="o">,</span> <span class="s">"未实现服务"</span><span class="o">);</span> <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"insititious"</span><span class="o">,</span> <span class="s">"未实现服务"</span><span class="o">);</span> <span class="n">map</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="s">"insincere"</span><span class="o">,</span> <span class="s">"已实现服务"</span><span class="o">);</span> <span class="kt">long</span> <span class="n">startTime</span> <span class="o">=</span> <span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">();</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">100000000</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">map</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"insincere"</span><span class="o">);</span> <span class="o">}</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"耗时(initialCapacity):"</span> <span class="o">+</span> <span class="o">(</span><span class="nc">System</span><span class="o">.</span><span class="na">currentTimeMillis</span><span class="o">()</span> <span class="o">-</span> <span class="n">startTime</span><span class="o">));</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>背景</strong>:HashMap 数据获取时间复杂度在 O(1) -&gt; O(logn) -&gt; O(n),但经过<em>特殊</em>操作,可以把这个时间复杂度,拉到O(n)</li> <li><strong>操作</strong>:这是一个定义<code class="highlighter-rouge">HashMap</code>存放业务实现key,通过key调用服务的功能。但这里的<code class="highlighter-rouge">key</code>,只有<code class="highlighter-rouge">insincere</code>有用,其他的都是未实现服务。那你看到有啥问题了吗? <ul> <li>这点代码乍一看没什么问题,看明白了就是代码里下砒霜!<strong>它的目的就一个,要让所有的key成一个链表放到HashMap中,而且把有用的key放到链表的最后,增加get时的耗时!</strong></li> <li>首先,<code class="highlighter-rouge">new HashMap&lt;&gt;(64);</code>为啥默认初始化64个长度?因为默认长度是8,插入元素时,当链表长度为8时候会进行扩容和链表树化判断,此时就会把原有的key散列了,不能让所有key构成一个时间复杂度较高的链表。</li> <li>其次,所有的 <code class="highlighter-rouge">key</code> 都是刻意选出来的,因为他们在 <code class="highlighter-rouge">HashMap</code> 计算下标时,下标值都为0,idx = <code class="highlighter-rouge">(size - 1) &amp; (key.hashCode() ^ (key.hashCode() &gt;&gt;&gt; 16))</code>,这样就能让所有 <code class="highlighter-rouge">key</code> 都散列到同一个位置进行碰撞。<em>而且单词 <code class="highlighter-rouge">insincere</code> 的意思是;<code class="highlighter-rouge">不诚恳的、不真诚的</code>!</em></li> <li>最后,前7个key其实都是废 <code class="highlighter-rouge">key</code>,不起任何作用,只有最后一个 key 有服务。那么这样就可以在HashMap中建出来很多这样耗时的碰撞链表,当然要满足<code class="highlighter-rouge">0.75</code>的负载因子,不要让HashMap扩容。</li> </ul> </li> </ul> <hr /> <p>其实很多算法包括:散列、倒排、负载等,都是可以用到很多实际的业务场景中的,包括:人群过滤、抽奖逻辑、数据路由等等方面,这些功能的使用可以降低时间复杂度,提升系统的性能,降低接口响应时常。</p> <h3 id="5-职责分离">5. 职责分离</h3> <p>为了可以让程序的逻辑实现更具有扩展性,通常我们都需要使用<a href="https://github.com/fuzhengwei/itstack-demo-design">设计模式</a>来处理各个场景的代码实现结构。而设计模式的使用在代码开发中的体现也主要为接口的定义、抽象类的包装和继承类的实现。通过这样的方式来隔离各个功能领域的开发,以此保障每次需求扩展时可以更加灵活的添加,而不至于让代码因需求迭代而变得更加混乱。</p> <p><strong>案例</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">interface</span> <span class="nc">IRuleExec</span> <span class="o">{</span> <span class="kt">void</span> <span class="nf">doRuleExec</span><span class="o">(</span><span class="nc">String</span> <span class="n">req</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">RuleConfig</span> <span class="o">{</span> <span class="kd">protected</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">String</span><span class="o">&gt;</span> <span class="n">configGroup</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentHashMap</span><span class="o">&lt;&gt;();</span> <span class="kd">static</span> <span class="o">{</span> <span class="c1">// ...</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">RuleDataSupport</span> <span class="kd">extends</span> <span class="nc">RuleConfig</span><span class="o">{</span> <span class="kd">protected</span> <span class="nc">String</span> <span class="nf">queryRuleConfig</span><span class="o">(</span><span class="nc">String</span> <span class="n">ruleId</span><span class="o">){</span> <span class="k">return</span> <span class="s">"xxx"</span><span class="o">;</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">AbstractRuleBase</span> <span class="kd">extends</span> <span class="nc">RuleDataSupport</span> <span class="kd">implements</span> <span class="nc">IRuleExec</span><span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">doRuleExec</span><span class="o">(</span><span class="nc">String</span> <span class="n">req</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 1. 查询配置</span> <span class="nc">String</span> <span class="n">ruleConfig</span> <span class="o">=</span> <span class="kd">super</span><span class="o">.</span><span class="na">queryRuleConfig</span><span class="o">(</span><span class="s">"10001"</span><span class="o">);</span> <span class="c1">// 2. 校验信息</span> <span class="n">checkRuleConfig</span><span class="o">(</span><span class="n">ruleConfig</span><span class="o">);</span> <span class="c1">// 3. 执行规则{含业务逻辑,交给业务自己处理}</span> <span class="k">this</span><span class="o">.</span><span class="na">doLogic</span><span class="o">(</span><span class="n">configGroup</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">ruleConfig</span><span class="o">));</span> <span class="o">}</span> <span class="cm">/** * 执行规则{含业务逻辑,交给业务自己处理} */</span> <span class="kd">protected</span> <span class="kd">abstract</span> <span class="kt">void</span> <span class="nf">doLogic</span><span class="o">(</span><span class="nc">String</span> <span class="n">req</span><span class="o">);</span> <span class="kd">private</span> <span class="kt">void</span> <span class="nf">checkRuleConfig</span><span class="o">(</span><span class="nc">String</span> <span class="n">ruleConfig</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// ... 校验配置</span> <span class="o">}</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">class</span> <span class="nc">RuleExec</span> <span class="kd">extends</span> <span class="nc">AbstractRuleBase</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">protected</span> <span class="kt">void</span> <span class="nf">doLogic</span><span class="o">(</span><span class="nc">String</span> <span class="n">req</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 封装自身业务逻辑</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <p><strong>类图</strong></p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-07.png" alt="" /></p> <ul> <li>这是一种模版模式结构的定义,使用到了接口实现、抽象类继承,同时可以看到在 <code class="highlighter-rouge">AbstractRuleBase</code> 抽象类中,是负责完成整个逻辑调用的定义,并且这个抽象类把一些通用的配置和数据使用单独隔离出去,而公用的简单方法放到自身实现,最后是关于抽象方法的定义和调用,而业务类 <code class="highlighter-rouge">RuleExec</code> 就可以按需实现自己的逻辑功能了。</li> </ul> <h3 id="6-逻辑缜密">6. 逻辑缜密</h3> <p>你的代码出过线上事故吗?为什么出的事故,是树上有十只鸟开一枪还剩几只的问题吗?比如:枪是无声的吗、鸟聋吗、有怀孕的吗、有绑在树上的鸟吗、边上的树还有鸟吗、鸟害怕枪声吗、有残疾的鸟吗、打鸟的人眼睛花不花,… …</p> <p>实际上你的线上事故基本回围绕在:数据库连接和慢查询、服务器负载和宕机、异常逻辑兜底、接口幂等性、数据防重性、MQ消费速度、RPC响应时常、工具类使用错误等等。</p> <p>下面举个例子:用户积分多支付,造成批量客诉。</p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-08.png" alt="" /></p> <ul> <li><strong>背景</strong>:这个产品功能的背景可能很大一部分研发都参与开发过,简单说就是满足用户使用积分抽奖的一个需求。上图左侧就是研发最开始设计的流程,通过RPC接口扣减用户积分,扣减成功后进行抽奖。但由于当天RPC服务不稳定,造成RPC实际调用成功,但返回超时失败。而调用RPC接口的uuid是每次自动生成的,不具备调用幂等性。所以造成了用户积分多支付现象。</li> <li><strong>处理</strong>:事故后修改抽奖流程,先生成待抽奖的抽奖单,由抽奖单ID调用RPC接口,保证接口幂等性。在RPC接口失败时由定时任务补偿的方式执行抽奖。流程整改后发现,补偿任务每周发生1~3次,那么也就是证明了RPC接口确实有可用率问题,同时也说明很久之前就有流程问题,但由于用户客诉较少,所以没有反馈。</li> </ul> <h3 id="7-领域聚合">7. 领域聚合</h3> <p>不够抽象、不能写死、不好扩展,是不是总是你的代码,每次都像一锤子买卖,完全是写死的、绑定的,根本没有一点缝隙让新的需求扩展进去。</p> <p>为什么呢,因为很多研发写出来的代码都不具有领域聚合的特点,当然这并不一定非得是在DDD的结构下,哪怕是在MVC的分层里,也一样可以写出很多好的聚合逻辑,把功能实现和业务的调用分离开。</p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-09.png" alt="" /></p> <ul> <li>依靠领域驱动设计的设计思想,通过事件风暴建立领域模型,合理划分领域逻辑和物理边界,建立领域对象及服务矩阵和服务架构图,定义符合DDD分层架构思想的代码结构模型,保证业务模型与代码模型的一致性。通过上述设计思想、方法和过程,指导团队按照DDD设计思想完成微服务设计和开发。 <ul> <li>拒绝泥球小单体、拒绝污染功能与服务、拒绝一加功能排期一个月</li> <li>架构出高可用极易符合互联网高速迭代的应用服务</li> <li>物料化、组装化、可编排的服务,提高人效</li> </ul> </li> </ul> <h3 id="8-服务分层">8. 服务分层</h3> <p>如果你想让你的系统工程代码可以支撑绝对多数的业务需求,并且能沉淀下来可以服用的功能,那么基本你就需要在做代码开发实现的时候,抽离出技术组件、功能领域和业务逻辑这样几个分层,不要把频繁变化的业务逻辑写入到各个功能领域中,应该让功能领域更具有独立性,可以被业务层串联、编排、组合实现不同业务需求。这样你的功能领域才能被逐步沉淀下来,也更易于每次需求都 扩展。</p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-10.png" alt="" /></p> <ul> <li>这是一个简化的分层逻辑结构,有聚合的领域、SDK组件、中间件和代码编排,并提供一些通用共性凝练出的服务治理功能。通过这样的分层和各个层级的实现方式,就可以更加灵活的承接需求了。</li> </ul> <h3 id="9-并发优化">9. 并发优化</h3> <p>在分布式场景开发系统,要尽可能运用上分布式的能力,从程序设计上尽可能的去避免一些集中的、分布式事物的、数据库加锁的,因为这些方式的使用都可能在某些极端情况下,造成系统的负载的超标,从而引发事故。</p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-11.png" alt="" /></p> <ul> <li>所以通常情况下更需要做去集中化处理,使用MQ消除峰,降低耦合,让数据可以最终一致性,也更要考虑在 Redis 下的使用,减少对数据库的大量锁处理。</li> <li>合理的运用MQ、RPC、分布式任务、Redis、分库分表以及分布式事务只有这样的操作你才可能让自己的程序代码可以支撑起更大的业务体量。</li> </ul> <h3 id="10-源码能力">10. 源码能力</h3> <p>你有了解过 HashMap 的拉链寻址数据结构吗、知道哈希散列和扰动函数吗、懂得怎么结合Spring动态切换数据源吗、AOP 是怎么实现以及使用的、MyBatis 是怎么和 Spring 结合交管Bean对象的,等等。看似都是些面试的八股文,但在实际的开发中其实是可以解决很多问题的。</p> <p><img src="https://bugstack.cn/assets/images/framework/framework-8-12.png" alt="" /></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Around</span><span class="o">(</span><span class="s">"aopPoint() &amp;&amp; @annotation(dbRouter)"</span><span class="o">)</span> <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">doRouter</span><span class="o">(</span><span class="nc">ProceedingJoinPoint</span> <span class="n">jp</span><span class="o">,</span> <span class="nc">DBRouter</span> <span class="n">dbRouter</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Throwable</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">dbKey</span> <span class="o">=</span> <span class="n">dbRouter</span><span class="o">.</span><span class="na">key</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="nc">StringUtils</span><span class="o">.</span><span class="na">isBlank</span><span class="o">(</span><span class="n">dbKey</span><span class="o">))</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">RuntimeException</span><span class="o">(</span><span class="s">"annotation DBRouter key is null!"</span><span class="o">);</span> <span class="c1">// 计算路由</span> <span class="nc">String</span> <span class="n">dbKeyAttr</span> <span class="o">=</span> <span class="n">getAttrValue</span><span class="o">(</span><span class="n">dbKey</span><span class="o">,</span> <span class="n">jp</span><span class="o">.</span><span class="na">getArgs</span><span class="o">());</span> <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">dbRouterConfig</span><span class="o">.</span><span class="na">getDbCount</span><span class="o">()</span> <span class="o">*</span> <span class="n">dbRouterConfig</span><span class="o">.</span><span class="na">getTbCount</span><span class="o">();</span> <span class="c1">// 扰动函数</span> <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="o">(</span><span class="n">size</span> <span class="o">-</span> <span class="mi">1</span><span class="o">)</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">dbKeyAttr</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">^</span> <span class="o">(</span><span class="n">dbKeyAttr</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">&gt;&gt;&gt;</span> <span class="mi">16</span><span class="o">));</span> <span class="c1">// 库表索引</span> <span class="kt">int</span> <span class="n">dbIdx</span> <span class="o">=</span> <span class="n">idx</span> <span class="o">/</span> <span class="n">dbRouterConfig</span><span class="o">.</span><span class="na">getTbCount</span><span class="o">()</span> <span class="o">+</span> <span class="mi">1</span><span class="o">;</span> <span class="kt">int</span> <span class="n">tbIdx</span> <span class="o">=</span> <span class="n">idx</span> <span class="o">-</span> <span class="n">dbRouterConfig</span><span class="o">.</span><span class="na">getTbCount</span><span class="o">()</span> <span class="o">*</span> <span class="o">(</span><span class="n">dbIdx</span> <span class="o">-</span> <span class="mi">1</span><span class="o">);</span> <span class="c1">// 设置到 ThreadLocal</span> <span class="nc">DBContextHolder</span><span class="o">.</span><span class="na">setDBKey</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%02d"</span><span class="o">,</span> <span class="n">dbIdx</span><span class="o">));</span> <span class="nc">DBContextHolder</span><span class="o">.</span><span class="na">setTBKey</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%02d"</span><span class="o">,</span> <span class="n">tbIdx</span><span class="o">));</span> <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"数据库路由 method:{} dbIdx:{} tbIdx:{}"</span><span class="o">,</span> <span class="n">getMethod</span><span class="o">(</span><span class="n">jp</span><span class="o">).</span><span class="na">getName</span><span class="o">(),</span> <span class="n">dbIdx</span><span class="o">,</span> <span class="n">tbIdx</span><span class="o">);</span> <span class="c1">// 返回结果</span> <span class="k">try</span> <span class="o">{</span> <span class="k">return</span> <span class="n">jp</span><span class="o">.</span><span class="na">proceed</span><span class="o">();</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="nc">DBContextHolder</span><span class="o">.</span><span class="na">clearDBKey</span><span class="o">();</span> <span class="nc">DBContextHolder</span><span class="o">.</span><span class="na">clearTBKey</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>这是 HashMap 哈希桶数组 + 链表 + 红黑树的数据结构,通过扰动函数 <code class="highlighter-rouge">(size - 1) &amp; (key.hashCode() ^ (key.hashCode() &gt;&gt;&gt; 16));</code> 解决数据碰撞严重的问题。</li> <li>但其实这样的散列算法、寻址方式都可以运用到数据库路由的设计实现中,还有整个数组+链表的方式其实库+表的方式也有类似之处。</li> <li>数据库路由简化的核心逻辑实现代码如上,首先我们提取了库表乘积的数量,把它当成 HashMap 一样的长度进行使用。</li> <li>当 idx 计算完总长度上的一个索引位置后,还需要把这个位置折算到库表中,看看总体长度的索引因为落到哪个库哪个表。</li> <li>最后是把这个计算的索引信息存放到 ThreadLocal 中,用于传递在方法调用过程中可以提取到索引信息。</li> </ul> <h2 id="三总结">三、总结</h2> <ul> <li>讲道理,你几乎不太可能把一堆已经烂的不行的代码,通过重构的方式把他处理干净。细了说,你要改变代码结构分层、属性对象整合、调用逻辑封装,但任何一步的操作都可能会对原有的接口定义和调用造成风险影响,而且外部现有调用你的接口还需要随着你的改动而升级,可能你会想着在包装一层,但这一层包装仍需要较大的时间成本和几乎没有价值的适配。</li> <li>所以我们在实际开发中,如果能让这些代码具有重构的可能,几乎就是要实时重构,每当你在添加新的功能、新的逻辑、修复异常时,就要考虑是否可以通过代码结构、实现方式、设计模式等手段的使用,改变不合理的功能实现。每一次,一点的优化和改变,也不会有那么难。</li> <li>当你在接需求的时候,认真思考承接这样的业务诉求,都需要建设怎样的数据结构、算法逻辑、设计模式、领域聚合、服务编排、系统架构等,才能更合理的搭建出良好的具有易维护、可扩展的系统服务。如果你对这些还没有什么感觉,可以阅读<a href="https://github.com/fuzhengwei/itstack-demo-design">设计模式</a>和<a href="https://github.com/fuzhengwei/small-spring">手写Spring</a>,这些内容可以帮助你提升不少的编程逻辑设计。</li> </ul> <h2 id="四系列推荐">四、系列推荐</h2> <ul> <li><a href="https://bugstack.cn/itstack-demo-any/2020/09/06/%E6%8F%A1%E8%8D%89-%E4%BD%A0%E7%AB%9F%E7%84%B6%E5%9C%A8%E4%BB%A3%E7%A0%81%E9%87%8C%E4%B8%8B%E6%AF%92.html">握草,你竟然在代码里下毒!</a></li> <li><a href="https://bugstack.cn/itstack-demo-any/2020/09/14/%E4%B8%80%E6%AC%A1%E4%BB%A3%E7%A0%81%E8%AF%84%E5%AE%A1-%E5%B7%AE%E7%82%B9%E8%BF%87%E4%B8%8D%E4%BA%86%E8%AF%95%E7%94%A8%E6%9C%9F.html">一次代码评审,差点过不了试用期!</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2021/01/03/%E8%B0%81%E8%AF%B4%E6%98%8E%E5%A4%A9%E4%B8%8A%E7%BA%BF-%E8%BF%99%E8%B4%A7%E5%8E%8B%E6%A0%B9%E4%B8%8D%E7%9F%A5%E9%81%93%E5%BC%80%E5%8F%91%E6%B5%81%E7%A8%8B.html">谁说明天上线,这货压根不知道开发流程!</a></li> <li><a href="https://bugstack.cn/framework/2021/08/22/%E5%B8%A6%E5%A4%B4%E6%92%B8%E9%A1%B9%E7%9B%AE-DDD-+-RPC-%E5%BC%80%E5%8F%91%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84-%E6%8A%BD%E5%A5%96%E7%B3%BB%E7%BB%9F.html">带头撸项目,《DDD + RPC 开发分布式架构,抽奖系统》</a></li> <li><a href="https://bugstack.cn/framework/2021/07/19/%E8%B0%83%E7%A0%94%E5%AD%97%E8%8A%82%E7%A0%81%E6%8F%92%E6%A1%A9%E6%8A%80%E6%9C%AF-%E7%94%A8%E4%BA%8E%E7%B3%BB%E7%BB%9F%E7%9B%91%E6%8E%A7%E8%AE%BE%E8%AE%A1%E5%92%8C%E5%AE%9E%E7%8E%B0.html">调研字节码插桩技术,用于系统监控设计和实现</a></li> </ul> Wed, 15 Sep 2021 00:00:00 +0800 https://bugstack.cn/framework/2021/09/15/%E8%BF%98%E9%87%8D%E6%9E%84-%E5%B0%B1%E4%BD%A0%E9%82%A3%E4%BB%A3%E7%A0%81%E5%8F%AA%E8%83%BD%E9%93%B2%E4%BA%86%E9%87%8D%E5%86%99.html https://bugstack.cn/framework/2021/09/15/%E8%BF%98%E9%87%8D%E6%9E%84-%E5%B0%B1%E4%BD%A0%E9%82%A3%E4%BB%A3%E7%A0%81%E5%8F%AA%E8%83%BD%E9%93%B2%E4%BA%86%E9%87%8D%E5%86%99.html java framework framework Hey there! 👋 2021版,开发者学习路线图分享! <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a> <br />原文:<a href="https://mp.weixin.qq.com/s/HyMzm9omIoIGz_bdKmL5Xg">https://mp.weixin.qq.com/s/HyMzm9omIoIGz_bdKmL5Xg</a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一介绍">一、介绍</h2> <p><code class="highlighter-rouge">Hey there! 👋 Roadmap to becoming a web developer in 2021</code></p> <p>前端、后端、运维,如果你不清楚自己要从哪开始,下个阶段要学什么,到哪里算是结束,可以参考下 <code class="highlighter-rouge">Kamran Ahmed</code> 整理的 Web 开发者 2021 版最新的学习路线图。</p> <p>以后端学习举例,这里包括你从接触互联网的基础内容开始,了解一部分如HTML、CSS的前端语言,之后学习操作系统的相关知识,并逐步转为后端语言学习,这包括你开始选择Java、C#、Go等,当一门基础语言掌握了以后,也就要逐步的开始提交你的代码,无论是工作需求还是个人积累代码,都会用到代码开发版本控制器的使用,以及你会发现GitHub这个神奇的网站。当你有了语言的编程能力,那么就要开始接触一系列用于存储数据库的技能学习,包括:关系型数据库和 NoSql 数据库已经缓存的应用,甚至可能你还会深入的研究如 Mysql 的内核和原理。接下来你的代码会经过测试、调试、验证、持续继承上线,以及验证它们是否安全可靠。此外你会慢慢的接触到真正的有价值的代码要在设计模式和系统架构的框架下进行开发,以及学习这西相关技巧,并且再也不只是单一的应用开发,还会用到分布式架构的相关技术,如:MQ、RPC、分布式任务、分库分表组件等,之后是对于这样大量应用的部署,可以运用容器化的能力,简化部署和提升弹性。可能一部分有特殊需求的研发还会涉及到学习图形数据库,WebSocket 等各项能力,在这个过程中也会涉及到一些运维知识,来侧面提升你的代码研发能力。</p> <p>当然这还不是结束,甚至可能永远没有结束!</p> <ul> <li>官网:<a href="https://roadmap.sh/">https://roadmap.sh/</a></li> <li>源码:<a href="https://github.com/kamranahmedse/developer-roadmap">https://github.com/kamranahmedse/developer-roadmap</a></li> <li>资料:学习路线高清图和对应的学习资料已经放入网盘,可以关注公众号:<code class="highlighter-rouge">bugstack虫洞栈</code> 回复:<code class="highlighter-rouge">网盘学习资料</code> 获取</li> </ul> <p><img src="https://bugstack.cn/assets/images/story/story-10-00.png" alt="" /></p> <h2 id="二学习路线">二、学习路线</h2> <h3 id="1-前端">1. 前端</h3> <p><img src="https://bugstack.cn/assets/images/story/story-10-01.png" alt="" /></p> <h3 id="2-后端">2. 后端</h3> <p><img src="https://bugstack.cn/assets/images/story/story-10-02.png" alt="" /></p> <h3 id="3-运维">3. 运维</h3> <p><img src="https://bugstack.cn/assets/images/story/story-10-03.png" alt="" /></p> <h2 id="三学习资料">三、学习资料</h2> <h3 id="1-技术内容">1. 技术内容</h3> <p>如果你对以上的学习路线有了较清楚的认识,但可能自己不好容易找到这些资料,那么可以参考我整理好的学习内容,包括对初学编程,大一、大二、大三、大四以及毕业了工作了几年后,都应该找什么样的资料学习的一个汇总。按照不同阶段的学习范围把资料分到不同的文件夹去,方便所处不同阶段的读者可以有一个相对准确的学习范围。</p> <p><img src="https://bugstack.cn/assets/images/story/story-4-5.png" alt="img" /></p> <ul> <li>资料明细:<a href="https://bugstack.cn/itstack-code-life/2020/03/31/大学四年到毕业工作5年的学习路线资源汇总.html">大学四年到毕业工作5年的学习路线资源汇总</a></li> <li>网盘链接:https://pan.baidu.com/s/4mmX7sDy - 资料较大,链接如果失效可以在公众号:bugstack虫洞栈 回复:<code class="highlighter-rouge">网盘学习资料</code></li> </ul> <h3 id="2-实战内容">2. 实战内容</h3> <p><strong>如果你已经跨过了初级阶段,需要做一些实践型的项目,可以关注下我的Github,如下:</strong></p> <p><img src="https://bugstack.cn/assets/images/story/story-7-05.png" alt="" /></p> <p><img src="https://bugstack.cn/assets/images/story/story-4-6.png" alt="img" /></p> <ul> <li><strong>内容</strong>:本代码库是作者小傅哥多年从事一线互联网Java开发的学习历程技术汇总,旨在为大家提供一个清晰详细的学习教程,侧重点更倾向编写Java核心内容。这部分资料也是我所写博客中实践项目的源码,在这里你可以学到Netty、字节码编程、设计模式、领域驱动设计、规则引擎、面试、架构以及职场经历的分享。</li> <li><strong>地址</strong>:<a href="https://github.com/fuzhengwei/CodeGuide/wiki">https://github.com/fuzhengwei/CodeGuide/wiki</a></li> </ul> <h2 id="四总结">四、总结</h2> <p>其实我们都是在经历着这样的一个学习阶段,时不时的就会到达某个不好突破的瓶颈期,就像:</p> <ol> <li>刚到公司理解不了项目插不进去手。</li> <li>能写一些代码逻辑了,但总感觉写的不好。</li> <li>设计模式慢慢用上了,但好像对整体架构又不太清楚。</li> <li>能游刃有余的接项目了,又感觉自己好像就是一个CRUD工具人,没有技术深度。</li> <li>想着要去扒开各种技术组件的源码看一看,但好像又有些看不懂,单个代码都懂,放一块不知道啥意思了。</li> <li>撸了一些源码后,又没多久就忘记了,很难把这些技术内容结合到一块去。</li> <li>开始尝试着做技术迁移,把在源码里学到的数据结构、算法逻辑,开始逐步用到自己的业务项目中了,感觉实现起来的逻辑有些深度了。但好像没有技术高度和全面的整合能力。</li> <li>开始做整体的架构设计,把代码逻辑转换成图和文字,总感觉不知道从哪下手描述,描述出来的东西,讲完了听众都没有啥反应。</li> <li>技术调研、经验积累、编写文章、提升影响力,一点点慢慢的平心静气的沉淀自己,是你接下来要完成的事情。</li> <li>路还很长,要铺宽度,也要挖深度,要懂得沟通,也要协调人员,不只是研发视角,还要有业务思维、产品逻辑、运营能力。</li> </ol> <p><strong>所以</strong>,我们要不断的去铺设自己的技术栈,做有成体系和有深度的学习,并把这西学到的能力运用在项目开发中,也要记录笔记整理资料,慢慢的才会形成自己一套完整的抗打的技术广度和深度。</p> <h2 id="五系列推荐">五、系列推荐</h2> <ul> <li><a href="https://bugstack.cn/spring/2021/08/12/%E6%89%8B%E6%92%B8-Spring-PDF-%E5%85%A8%E4%B9%A6260%E9%A1%B56.5%E4%B8%87%E5%AD%97-%E5%AE%8C%E7%A8%BF&amp;%E5%8F%91%E7%89%88.html">《手撸 Spring》PDF,全书260页6.5万字,完稿&amp;发版!</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2020/12/06/90-%E7%9A%84%E7%A8%8B%E5%BA%8F%E5%91%98-%E9%83%BD%E6%B2%A1%E7%94%A8%E8%BF%87%E5%A4%9A%E7%BA%BF%E7%A8%8B%E5%92%8C%E9%94%81-%E6%80%8E%E4%B9%88%E6%88%90%E4%B8%BA%E6%9E%B6%E6%9E%84%E5%B8%88.html">90%的程序员,都没用过多线程和锁,怎么成为架构师?</a></li> <li><a href="https://bugstack.cn/interview/2020/11/25/%E9%9D%A2%E7%BB%8F%E6%89%8B%E5%86%8C-%E7%AC%AC19%E7%AF%87-Thread.start()-%E5%AE%83%E6%98%AF%E6%80%8E%E4%B9%88%E8%AE%A9%E7%BA%BF%E7%A8%8B%E5%90%AF%E5%8A%A8%E7%9A%84%E5%91%A2.html">Thread.start() ,它是怎么让线程启动的呢?</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2020/11/15/BATJTMD-%E5%A4%A7%E5%8E%82%E6%8B%9B%E8%81%98-%E9%83%BD%E6%8B%9B%E4%BB%80%E4%B9%88%E6%A0%B7Java%E7%A8%8B%E5%BA%8F%E5%91%98.html">BATJTMD,大厂招聘,都招什么样Java程序员?</a></li> <li><a href="https://bugstack.cn/framework/2021/02/28/%E5%B7%A5%E4%BD%9C%E4%B8%A4%E4%B8%89%E5%B9%B4-%E6%95%B4%E4%B8%8D%E6%98%8E%E7%99%BD%E6%9E%B6%E6%9E%84%E5%9B%BE%E9%83%BD%E7%94%BB%E5%95%A5.html">工作两三年了,整不明白架构图都画啥?</a></li> </ul> Thu, 09 Sep 2021 00:00:00 +0800 https://bugstack.cn/itstack-code-life/2021/09/09/2021%E7%89%88-%E5%BC%80%E5%8F%91%E8%80%85%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF%E5%9B%BE%E5%88%86%E4%BA%AB.html https://bugstack.cn/itstack-code-life/2021/09/09/2021%E7%89%88-%E5%BC%80%E5%8F%91%E8%80%85%E5%AD%A6%E4%B9%A0%E8%B7%AF%E7%BA%BF%E5%9B%BE%E5%88%86%E4%BA%AB.html java itstack-code-life itstack-code-life 靠写文章,我在CSDN赚了1.27万! <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a> <br />原文:<a href="https://mp.weixin.qq.com/s/PIzReLEPzY-f2pqr2k5RPQ">https://mp.weixin.qq.com/s/PIzReLEPzY-f2pqr2k5RPQ</a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一嘿赚了钱嘞">一、嘿,赚了钱嘞</h2> <p>💥<code class="highlighter-rouge">CSDN 5.1K下载量,可提现收入1.2W!</code></p> <p>写了两年博客,用了两年的4个服务器,终于能回点本了,一年前还得靠薅羊毛填补服务器成本。讲道理真难,能坚持下来真难!晒个图,看看傅哥是怎么用 <code class="highlighter-rouge">2.94</code>、<code class="highlighter-rouge">2.94</code>、<code class="highlighter-rouge">2.94</code> 的赚到1万多块的。</p> <p><img src="https://bugstack.cn/assets/images/story/story-8-01.png" alt="" /></p> <p>1.3W 多不多?其实可能真的不多,这1.3W 的收入用了将近9个月,写了40万字和绘制了上百张PPT图稿,最后整理出 3 本在售的 PDF 资料。</p> <p>一直以来我都希望在技术的方向上做有价值的内容,即使是用这样辛苦编写整理出来的资料赚点收入,也更希望是知识内容远大于付费成本。其实想到这些有时候感觉各类技术推文广告可以更实在的方式宣传,通过介绍课程的核心内容、适合人群、学到什么,也能很好的帮助一些编程路上持续学习成长的码农。但可能目前恰恰相反,广告的标题太吓人了,甚至有种只要学了这个课就能干翻这个<code class="highlighter-rouge">码农世界</code>升P7当架构的感觉,但学完了却被有些培训机构干的服服帖帖的。</p> <p>写付费专栏可以、出PDF资料也行、弄知识星球也好,不太可能任何一个技术号主或者培训平台都用爱发电,因为我们从这些平台学到的知识也是用在工作、跳槽、晋升答辩上,来提升自己的福利待遇。只要做的知识不忽悠人、内容干净、确认学习内容有帮助,那么就这样的铺路人和走路人,都能一起走的长远。</p> <p><em>恶意广告就像铺的烂路、白皮袄和窃权就像挖坑的,两者加一块,早早晚晚会掉坑里!</em></p> <h2 id="二哎挺不容易">二、哎,挺不容易</h2> <p><code class="highlighter-rouge">做公众号,我可能选择最难的模式!</code></p> <p>不转载、不接投稿、不发广告,2年多我似乎是拒绝了流量也拒绝了收入,那为什么呢?因为我想专心铺我的技术栈内容广度和深度,成体系的建设知识内容完善度,一者补全自己的漏洞提升写作能力、二者帮助也在这条路上奔走的技术同好共同成长。</p> <p>而转载和投稿的技术文章,并不是我亲自写的内容,当有粉丝向我咨询文章中的学习内容时,我会很难解释这个内容不是我写的,我不清楚这样的话。总感觉这样是有那么一点点心里上的不舒服,也有那么一些不负责的感觉,再有就是这类文章可能已经铺天盖地的火热了,我就没必要再做同质化内容的凑热闹了。</p> <p>在做技术公众号的这条路上,我选择的是长期<code class="highlighter-rouge">原创文章</code>和<code class="highlighter-rouge">深耕内容</code>,从几乎没有几个人的阅读量时保持初心的做到现在,也是希望可以把 <strong>沉淀、分享、成长,让自己和他人都能有所收获</strong> 坚持下来。</p> <h2 id="三呐画个路线">三、呐,画个路线</h2> <p><code class="highlighter-rouge">像我一样,有章法的学习起来!</code></p> <p>现在我的技术栈内容已经慢慢铺设的挺完整了,有基础的核心内容、有提升的拓展文章、有实战的锻炼项目,其实这些也是我从初学编程阶段走来的学习方式,虽然,这样的是慢了些,但基础夯实的厚度够了以后再学其他的就不那么容易拉跨了🌶。</p> <p><strong>学习路线</strong></p> <p><img src="https://bugstack.cn/assets/images/story/story-8-02.png" alt="" /></p> <p>在学会写代码到能把代码写好,是一个较有长度的时间过长,这个过程中需要把基础夯实,可能很大一部分研发伙伴不知道夯实的是什么,其实不单单把 Java 的 API 用熟练,而是能把 Java 中的一些核心源码设计学会,包括像HashMap、ArrayList、AQS、并发锁等所涉及到的数据结构和算法逻辑弄清楚,才是真正的夯实基础。</p> <p>在夯实基础完成后接下来就不只是单单的用代码实现所需要完成的业务逻辑了,还要考虑到怎么能做到<code class="highlighter-rouge">易维护</code>、<code class="highlighter-rouge">可扩展</code>,这个时候你就需要了解设计模式了,在经过设计模式的学习以及到项目中的实战后,会开始陆续的考虑代码的功能实现,而不是大量的堆砌ifelse来完成需求开发。</p> <p>如果能对设计模式有一定的了解后,后面就可以尝试学习下 Spring、Mybatis 这样的框架源码了,尤其是 Spring 它的结构设计和代码实现用了大量的设计模式,也对接口、抽象类、继承、封装有丰富的使用场景,当你学习完成这部分内容后就会对 Spring 把 Bean 对象解耦到可以扩展各类节点插入逻辑的设计,感觉到异常强大。其实所有的设计模式体现也就是对逻辑职责的分拆,让各个类包装处理各个业务内容,不至于被一个堆业务把一个类填充到炸裂。</p> <p>当你有上面👆这些基础以后,但又没有一个可以让你操刀的项目时,那么就可以参与到 <a href="https://t.zsxq.com/jAi2nUf"><code class="highlighter-rouge">Lottery 抽奖系统 - DDD 实践</code></a> 的项目中了,在此项目中你会学习到互联网公司关于C端项目开发时候用到的一些,技术、架构、规范等内容。由于项目为实战类编程项目,在学习的过程中需要上手操作,小傅哥会把系统的搭建拉不同的分支列为每一个章节进行设计和实现并记录到开发日记中,读者在学习的过程中可以结合这部分内容边看文章边写代码实践。<code class="highlighter-rouge">技术:SpringBoot、Mybatis、Dubbo、MQ、Redis、Mysql、ELK、分库分表、Otter</code> <code class="highlighter-rouge">架构:DDD 领域驱动设计、充血模型、设计模式</code> <code class="highlighter-rouge">规范:分支提交规范、代码编写规范</code></p> <p><strong>资料介绍</strong></p> <ul> <li><a href="https://download.csdn.net/download/Yao__Shun__Yu/14932325">小傅哥的《Java 面经手册》</a> ⭐⭐⭐⭐ 3.8k+ 下载量</li> </ul> <blockquote> <p>全书共计 5 章 29 节,417页11.5万字,耗时 4 个月完成。涵盖数据结构、算法逻辑、并发编程、JVM以及简历和互联网大厂面试等内容。但此书并不是单纯的面试题,也不是内卷八股文。而是从一个单纯的和程序员有关的数学知识点开始,深入讲解 Java 的核心技术。并且每一章节都配有实践验证的源码,可以对照着一起撸才更有感觉!</p> </blockquote> <ul> <li><a href="https://item.jd.com/13218336.html">小傅哥的《重学 Java 设计模式》</a> ⭐⭐⭐⭐</li> </ul> <blockquote> <p>本书是作者<code class="highlighter-rouge">小傅哥</code>,基于互联网真实案例编写的Java设计模式实践图书。全书以解决方案为核心,从实际开发业务中抽离出交易、营销、规则引擎、中间件、框架源码等22个真实场景,对设计模式进行全面、彻底的分析。帮助读者灵活地使用各种设计模式,从容应对复杂变化的业务需求,编写出易维护、可扩展的代码结构。</p> </blockquote> <ul> <li><a href="https://download.csdn.net/download/Yao__Shun__Yu/21009038">小傅哥的《手撸 Spring》</a> ⭐⭐⭐⭐⭐</li> </ul> <blockquote> <p>通过带着读者手写简化版 Spring 框架,了解 Spring 核心原理。在手写Spring 源码的过程中会摘取整体框架中的核心逻辑,简化代码实现过程,保留核心功能,例如:IOC、AOP、Bean生命周期、上下文、作用域、资源处理等内容实现。</p> </blockquote> <ul> <li><a href="https://t.zsxq.com/jAi2nUf">Lottery 抽奖系统 - 基于领域驱动设计的四层架构实践</a> ⭐⭐⭐⭐</li> </ul> <blockquote> <p>Lottery 抽奖系统,项目是一款互联网面向C端人群营销活动类的抽奖系统,可以提供抽奖活动玩法策略的创建、参与、记账、发奖等逻辑功能。在使用的过程中运营人员通过创建概率类奖品的抽奖玩法,对用户进行拉新、促活、留存,通常这样的系统会用在电商、外卖、出行、公众号运营等各类场景中。</p> </blockquote> <h2 id="四呀不止码农">四、呀,不止码农</h2> <p><code class="highlighter-rouge">发现没,傅哥并不只是一个码农!</code></p> <p>其实我们大部分码农在公司都只是像一个零件或者工具,不了解业务、不懂得产品、不知晓运营,也就是在开发、测试、上线这一环上有所储备。所以其实我们也很多时候都在站在自己的视角去说业务、喷产品、叼运营,但如果这些事放在自己干,会是什么样呢?</p> <p>不知道从什么时候开始,我挺希望把自己的想法落地,有时候喜欢去讨论下产品、有时候喜欢去问问运营手段、甚至还对UI的设计感了兴趣。因为这些职责完完整整的加到一块,才是一个全面的 <strong>“产品”</strong>。</p> <p>而有些时候我听到的这不能赋能业务、那不能帮助产品,虽然这些可能是大战略,但对于个人的学习成长来说,8小时以外是不能被OKR限制的,总得要落地些什么自己想做的。才有机会更全面的看懂这个 <strong>“产品”</strong> 的全貌。</p> <p><strong>学习运营</strong></p> <p><img src="https://bugstack.cn/assets/images/story/story-8-03.png" alt="" /></p> <ul> <li>运营的过程是从流程、产品、用户、需求到心智的过程,也就是不断的筛选出目标用户。而我的目标用户就是那些可以一起学习成长的技术同好。</li> <li>所以我目前在走的路线也是以这个过程在执行的,不只是关注最初的吸引流量,还包括完成开发产品,解决需求和培养心智的过程。像付费很低的技术价值较高的PDF资料,其实更属于一种流量产品。就像宝马1系一样,其实卖一辆就赔一辆,但它确实建设整个流量链条的通道。</li> <li>当然,我还是不是运营大佬,至少算不上一个自媒体人,对流量的玩法的还比较青涩。也只能希望在坚持我的初心干净的技术人路上,不断的成长起来。<em>别总是在自己小圈子里晃悠,抬抬眼看看整个全貌。</em></li> </ul> <h2 id="五来送点奖品">五、来,送点奖品</h2> <blockquote> <p>送一波福利奖品🏅,感谢粉丝伙伴的陪同和支持!</p> </blockquote> <h3 id="1-礼品说明">1. 礼品说明</h3> <ul> <li>一等奖[1名]:小霸王红白机</li> <li>二等奖[9名]:game poke 怀旧款掌上小型游戏机,挺卡哇伊的</li> </ul> <h3 id="2-得奖规则">2. 得奖规则</h3> <ul> <li>对公众号此文章:<a href="https://mp.weixin.qq.com/s/PIzReLEPzY-f2pqr2k5RPQ">https://mp.weixin.qq.com/s/PIzReLEPzY-f2pqr2k5RPQ</a> 进行<code class="highlighter-rouge">留言并转发朋友圈</code>,找伙伴给你的留言点赞</li> <li>以个人留言<code class="highlighter-rouge">被读者点赞数量</code>为排名,最高的前10名依次获得对应奖品,最低点赞数量 &gt;= 10</li> <li>仅有100个留言名额,超过数量不能被精选,参与活动</li> </ul> <h3 id="3-活动说明">3. 活动说明</h3> <ul> <li><strong>时间范围</strong>:2021-09-06 07:55:00 - 2020-09-08 07:59:59,共计2天计票</li> <li><strong>公布时间</strong>:2021-09-10,星期五</li> <li><strong>公布方式</strong>:小傅哥的朋友圈公布,<em>记得添加小傅哥微信:<code class="highlighter-rouge">fustack</code></em></li> <li><strong>领奖方式</strong>:看到小傅哥朋友圈后,主动联系小傅哥。提供;收获人、收获地址、手机号等必要信息。<em>😄嘿…嘿,我会保密的你的信息!</em></li> </ul> <h2 id="六好总结一下">六、好,总结一下</h2> <p>做公众号写文章,一路走来学到了很多,懂得了很多,也锻炼了不少。有几条很重要的建议可能对你也有帮助:</p> <ul> <li>有自己的思考,不要人云亦云</li> <li>坚持做好自己的事情,哪怕是慢一点</li> <li>不违背自己的初心,也不要被诱惑</li> <li>把时间放在可以积累长期价值的事上</li> <li>生活和职业上都提早规划好自己的路,别被牵着走</li> <li>有自己的个性,不要被既得利益者忽悠成螺丝钉</li> <li>坚持阅读和写作输出,积累个人的影响力</li> <li>坚持跑步锻炼身体,哪怕不是为了减肥</li> <li>有些时候可能不是你做的不优秀,只是入场时间不对,可以换个场地</li> <li>与同好同行,共同进步</li> </ul> <p>加油:让我们既有底气、又有勇气,的往前冲!</p> <h2 id="七呐系列推荐">七、呐,系列推荐!</h2> <ul> <li><a href="https://bugstack.cn/itstack-code-life/2021/05/26/%E5%B0%8F%E5%82%85%E5%93%A5-%E4%B8%80%E4%B8%AA%E6%9C%89-%E5%89%AF%E4%B8%9A-%E7%9A%84%E7%A0%81%E5%86%9C.html">小傅哥,一个有“副业”的码农!</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2021/01/17/%E6%95%B0%E5%AD%A6-%E7%A6%BB%E4%B8%80%E4%B8%AA%E7%A8%8B%E5%BA%8F%E5%91%98%E6%9C%89%E5%A4%9A%E8%BF%91.html">数学,离一个程序员有多近?</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2020/12/20/%E5%B7%A5%E4%BD%9C3%E5%B9%B4-%E7%9C%8B%E5%95%A5%E8%B5%84%E6%96%99%E8%83%BD%E6%9C%88%E8%96%AA30K.html">工作3年,看啥资料能月薪30K?</a></li> <li><a href="https://bugstack.cn/itstack-demo-any/2020/09/14/%E4%B8%80%E6%AC%A1%E4%BB%A3%E7%A0%81%E8%AF%84%E5%AE%A1-%E5%B7%AE%E7%82%B9%E8%BF%87%E4%B8%8D%E4%BA%86%E8%AF%95%E7%94%A8%E6%9C%9F.html">一次代码评审,差点过不了试用期!</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2021/05/09/%E5%A4%A7%E5%AD%A6%E6%AF%95%E4%B8%9A%E8%A6%81%E5%86%99%E5%A4%9A%E5%B0%91%E8%A1%8C%E4%BB%A3%E7%A0%81-%E6%89%8D%E8%83%BD%E4%B8%8D%E7%94%A8%E8%8A%B1%E9%92%B1%E5%9F%B9%E8%AE%AD%E5%B0%B1%E6%89%BE%E5%88%B0%E4%B8%80%E4%BB%BD%E5%BC%80%E5%8F%91%E5%B7%A5%E4%BD%9C.html">大学毕业要写多少行代码,才能不用花钱培训就找到一份开发工作?</a></li> </ul> Sun, 05 Sep 2021 00:00:00 +0800 https://bugstack.cn/itstack-code-life/2021/09/05/%E6%88%91%E5%9C%A8CSDN%E8%B5%9A%E4%BA%861.2%E4%B8%87.html https://bugstack.cn/itstack-code-life/2021/09/05/%E6%88%91%E5%9C%A8CSDN%E8%B5%9A%E4%BA%861.2%E4%B8%87.html java itstack-code-life itstack-code-life 技术调研,IDEA 插件怎么开发「脚手架、低代码可视化编排、接口生成测试」? <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a> <br />原文:<a href="https://mp.weixin.qq.com/s/ckbu4ej4P2fEA8D_5cNUpw">https://mp.weixin.qq.com/s/ckbu4ej4P2fEA8D_5cNUpw</a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一前言">一、前言</h2> <p><code class="highlighter-rouge">不踩些坑,根本不是成熟的码农!</code></p> <p>你觉得肯德基全家桶是什么?一家人一起吃的桶吗,就那么一点点?不是,肯德基全家桶说的是,鸡的全家桶!</p> <p>听到这个故事就像有时候我因为需要解决某些问题去<code class="highlighter-rouge">搜索</code>、<code class="highlighter-rouge">折腾</code>、<code class="highlighter-rouge">验证</code>、<code class="highlighter-rouge">排除</code>的技术方案,因为方向不对,所以努力也就白费。只能一次次在众多的资料、文档、源码中一点点找到并组合出适合自己的问题场景的技术处理手段。</p> <p>但这个过程有时候又是必须经历的,很少有时候能一次就找到正确的答案或者人,哪怕开始就找到了,也会再去排查下其他的资料,看看还有没有更好的。<em>是不,这就是你吧?</em></p> <h2 id="二抛出问题">二、抛出问题</h2> <p><code class="highlighter-rouge">我又要冲IDEA插件开发了!</code></p> <p>在研究字节码插桩的相关技术后,🤔考虑着除了通常的用在代码上线后的非入侵式监控外,是不是也可以用于研发在开发阶段对系统接口的提取呢?</p> <p>带着这个从脑袋中冒出的想法,想到如果要处理这个事情,最核心的问题就是开发一款IDEA插件+字节码插桩能力,在代码运行时对运行方法增强,提取相关的必要信息。别说案例还真做出来了,如下:</p> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-1.png" alt="" /></p> <ul> <li>案例地址:<a href="https://bugstack.cn/framework/2021/02/04/%E5%9F%BA%E4%BA%8EIDEA%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%92%8C%E5%AD%97%E8%8A%82%E7%A0%81%E6%8F%92%E6%A1%A9%E6%8A%80%E6%9C%AF-%E5%AE%9E%E7%8E%B0%E7%A0%94%E5%8F%91%E4%BA%A4%E4%BB%98%E8%B4%A8%E9%87%8F%E8%87%AA%E5%8A%A8%E5%88%86%E6%9E%90.html">基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析</a></li> <li>后续问题:其实实现到这里还只能算是一个案例,对于 IDEA 插件开发能力并没有完全弄透,比如这个 IDEA 插件需要做一些基础配置,那么在哪里打开呢?还有实时监控并产生的接口信息能在 IDEA 界面右侧展示出来或者支持导出吗?如果我再有一些集合 IDEA 插件开发的能力做的其他的功能引入咋办呢?这里用到了哪些技术呢?等等,这些问题都需要去一一解决掉,才能完完整整的开发一个可用的 IDEA 插件,为此,需要做更深入的资料整理和实践验证。</li> </ul> <h2 id="三开发插件涉及的问题">三、开发插件涉及的问题</h2> <p><strong>问题汇总</strong>:开发一个 IDEA 插件基本要涉及到的问题过程如下:</p> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-2.png" alt="" /></p> <ul> <li><strong>开发方式</strong>:在官网的描述中,创建IDEA插件工程的方式有两种分别是,IntelliJ Platform Plugin 模版创建和 Gradle 构建方式。</li> <li><strong>框架入口</strong>:一个 IDEA 插件开发完,要考虑把它嵌入到哪,比如是从 IDEA 窗体的 Edit、Tools 等进入配置还是把窗体嵌入到左、右工具条还是IDEA窗体下的对话框。</li> <li><strong>UI</strong>:思考的是窗体需要用到什么语言开发,没错,用的就是 Swing、Awt 的技术能力。</li> <li><strong>API</strong>:在 IDEA 插件开发中,一般都是围绕工程进行的,那么基本要从通过 IDEA 插件 JDK 开发能力中获取到工程信息、类信息、文件信息等。</li> <li><strong>外部功能</strong>:这一个是用于把插件能力与外部系统结合,比如你是需要把拿到的接口上传到服务器,还是从远程下载文件等等。</li> </ul> <h2 id="四开发插件的两种配置">四、开发插件的两种配置</h2> <ul> <li>官方文档:<a href="https://plugins.jetbrains.com/docs/intellij/disposers.html">https://plugins.jetbrains.com/docs/intellij/disposers.html</a></li> <li>官方案例:<a href="https://github.com/JetBrains/intellij-sdk-docs">https://github.com/JetBrains/intellij-sdk-docs</a></li> </ul> <h3 id="1-基础配置">1. 基础配置</h3> <ol> <li>IntelliJ IDEA 2019.3.1 x64</li> <li>JDK 需要配置 IntelliJ Platform Plugin JDK,在 Project Setting 中设置,这样才可以正常开发 IDEA 插件</li> <li>id ‘org.jetbrains.intellij’ version ‘0.6.3’</li> <li>gradle-5.2.1 <code class="highlighter-rouge">与 2019 IDEA 版本下的插件开发匹配</code></li> <li> <p>Settings -&gt; Build, Execution,Deloyment -&gt; Build Tools,配置 Gradle。Gradle user home = <code class="highlighter-rouge">D:/Program Files (x86)/gradle/gradle-5.2.1/.gradle</code> User Gradle from =<code class="highlighter-rouge">gradle-wrapper.properties</code> 或者 <code class="highlighter-rouge">Specified location</code> 具体如下图:</p> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-3.png" alt="" /></p> </li> </ol> <p>如果你是使用 IDEA New Project 默认的 IntelliJ Platform Plugin 方式,其实只关注1、2两步骤就可以了,但如果你需要 Gradle,那么需要注意3、4、5步骤的设置。当然通常也更推荐使用 Gradle 来搭建工程,这样你在需要一些额外的 Jar 包时候,只需要在 Gradle <code class="highlighter-rouge">build.gradle</code> 配置即可,而不是把需要的 Jar 包复制到工程的 lib 下。</p> <h3 id="2-遇到问题">2. 遇到问题</h3> <p>在使用 Gradle 构建项目后,你会遇到几个问题;</p> <ol> <li>提前下载好 Gradle 5.2.1 版本并配置上,否则构建工程自动下载会比较慢 <a href="https://gradle.org/next-steps/?version=5.2.1&amp;format=all">https://gradle.org/next-steps/?version=5.2.1&amp;format=all</a></li> <li>构建工程时候拉取相关内容,会比较慢,如果你有代理会好一些。</li> <li>【麻烦的问题】基于 Gradle 的 IDEA 插件开发会在构建过程中,会下载一个匹配版本的 IDEA 软件用于启动测试开发插件,几百兆那种zip包 <code class="highlighter-rouge">ideaIC-2019.3.1.zip</code>。这个时候基本你会遇到一个崩溃的报错 ` Could not resolve all files for configuration ‘:detachedConfiguration1’.<code class="highlighter-rouge"> 咋办呢,如果你不嫌弃麻烦可以手动下载并SHA1加密后把下载的文件放到缓存文件夹中 </code>.gradle\caches\modules-2\files-2.1` 具体操作如下: <ul> <li>打开系统盘下当前用户的<code class="highlighter-rouge">.gradle</code>目录,进入<code class="highlighter-rouge">.gradle\caches\modules-2\files-2.1</code>目录,即为缓存文件的目录。这个目录是你的报错构建过程中的报错地址,<code class="highlighter-rouge">Could not get resource D:\Program Files (x86)\gradle\gradle-5.2.1\.gradle\caches\modules-2\files-2.1\com.jetbrains.intellij.idea\ideaIC\2019.3.1</code></li> <li>加密文件夹<code class="highlighter-rouge">2dae8e50d4b0508cad2e680b53414f657954f390</code>目录名称(你的可能不是这样的),我去,这个应该是<a href="http://msd.misuland.com/pd/4146263708462488226">加密</a>过的,但是是什么加密呢?,经过了解知道了这个是<code class="highlighter-rouge">SHA1</code>加密,且是对文件进行<code class="highlighter-rouge">SHA1</code>的加密生成的唯一<a href="http://msd.misuland.com/pd/4146263708462488416">字符串</a>,但是windows上没有这个命令,在线<code class="highlighter-rouge">SHA1</code>也太麻烦了,还要上传文件,于是想到了Java的API,还有就是通过<code class="highlighter-rouge">git hash</code>命令行来实现。 把我们的文件<code class="highlighter-rouge">ideaIC-2019.3.1.zip</code>先临时拷贝到这个目录。运行<code class="highlighter-rouge">sha1sum.exe ideaIC-2019.3.1.zip</code>命令,生成唯一的唯一字符串(用来校验文件的完整性),这样就拿到这个<code class="highlighter-rouge">2dae8e50d4b0508cad2e680b53414f657954f390</code>目录名</li> <li>接下来在<code class="highlighter-rouge">2019.3.1</code>目录下,新建目录<code class="highlighter-rouge">2dae8e50d4b0508cad2e680b53414f657954f390</code>,将<code class="highlighter-rouge">ideaIC-2019.3.1.zip</code>移动进去即可。</li> </ul> </li> <li>【堆栈溢出】在 Gradle 构建的过程中,消耗内存较大,可能会报错 <code class="highlighter-rouge">Java heap space</code> 所以也可以 在IDEA项目根目录下,新建文件<code class="highlighter-rouge">gradle.properties</code>,添加如下内容,变更gradle Jvm参数 <code class="highlighter-rouge">org.gradle.jvmargs=-Xmx2024m -XX:MaxPermSize=512m</code> 别说还挺好用,竟然构建成功了。</li> </ol> <h2 id="五写个测试案例">五、写个测试案例</h2> <h3 id="1-工程结构">1. 工程结构</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">PluginGuide</span> <span class="err">├──</span> <span class="o">.</span><span class="na">gradle</span> <span class="err">└──</span> <span class="n">src</span> <span class="err">├──</span> <span class="n">main</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">java</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">HiClazz</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">MyDumbAwareAction</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">MySearchableConfigurable</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">MyToolWindowFactory</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">└──</span> <span class="nc">TestUI</span><span class="o">.</span><span class="na">java</span> <span class="err">└──</span> <span class="n">resources</span> <span class="err">├──</span> <span class="n">icons</span> <span class="err">└──</span> <span class="no">META</span><span class="o">-</span><span class="no">INF</span> <span class="err">└──</span> <span class="n">plugin</span><span class="o">.</span><span class="na">xml</span> </code></pre></div></div> <ul> <li>HiClazz 是继承 AnAction 的实现类,用于附着到 IDEA 的窗体上,点击后打开对应页面</li> <li>MyDumbAwareAction、MyToolWindowFactory,配合使用,用于在 IDEA 最下面的窗体设置,与你看见的控制台输出信息位置一样。</li> <li>MySearchableConfigurable,可以用于 Settings 中配置窗体。</li> <li>TestUI 是基于 Swing 开发的窗体,验证在 AnAction 实现类中打开。</li> <li>plugin.xml 是整个 IDEA 咖啡的配置文件,你所有的窗体都会在这个配置文件里有所体现。</li> </ul> <h3 id="2-anaction">2. AnAction</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">HiClazz</span> <span class="kd">extends</span> <span class="nc">AnAction</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">actionPerformed</span><span class="o">(</span><span class="nc">AnActionEvent</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span> <span class="nc">Project</span> <span class="n">project</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="nc">PlatformDataKeys</span><span class="o">.</span><span class="na">PROJECT</span><span class="o">);</span> <span class="nc">PsiFile</span> <span class="n">psiFile</span> <span class="o">=</span> <span class="n">e</span><span class="o">.</span><span class="na">getData</span><span class="o">(</span><span class="nc">CommonDataKeys</span><span class="o">.</span><span class="na">PSI_FILE</span><span class="o">);</span> <span class="nc">String</span> <span class="n">classPath</span> <span class="o">=</span> <span class="n">psiFile</span><span class="o">.</span><span class="na">getVirtualFile</span><span class="o">().</span><span class="na">getPath</span><span class="o">();</span> <span class="nc">String</span> <span class="n">title</span> <span class="o">=</span> <span class="s">"Hello World!"</span><span class="o">;</span> <span class="nc">Messages</span><span class="o">.</span><span class="na">showMessageDialog</span><span class="o">(</span><span class="n">project</span><span class="o">,</span> <span class="n">classPath</span><span class="o">,</span> <span class="n">title</span><span class="o">,</span> <span class="nc">Messages</span><span class="o">.</span><span class="na">getInformationIcon</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>测试在 IDEA 中读取鼠标停留在类文件中的信息。我们可以把这个 AnAction 配置到各个 IDEA 菜单中。</li> </ul> <h3 id="3-mytoolwindowfactory">3. MyToolWindowFactory</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MyToolWindowFactory</span> <span class="kd">implements</span> <span class="nc">ToolWindowFactory</span> <span class="o">{</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">createToolWindowContent</span><span class="o">(</span><span class="nd">@NotNull</span> <span class="nc">Project</span> <span class="n">project</span><span class="o">,</span> <span class="nd">@NotNull</span> <span class="nc">ToolWindow</span> <span class="n">toolWindow</span><span class="o">)</span> <span class="o">{</span> <span class="n">toolWindow</span><span class="o">.</span><span class="na">setToHideOnEmptyContent</span><span class="o">(</span><span class="kc">true</span><span class="o">);</span> <span class="kd">class</span> <span class="nc">MyPanel</span> <span class="kd">extends</span> <span class="nc">SimpleToolWindowPanel</span> <span class="o">{</span> <span class="kd">public</span> <span class="nf">MyPanel</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">vertical</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">(</span><span class="n">vertical</span><span class="o">);</span> <span class="nc">DefaultActionGroup</span> <span class="n">group</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DefaultActionGroup</span><span class="o">();</span> <span class="n">group</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">MyDumbAwareAction</span><span class="o">(</span><span class="s">"Login1"</span><span class="o">));</span> <span class="n">group</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">MyDumbAwareAction</span><span class="o">(</span><span class="s">"Login2"</span><span class="o">));</span> <span class="n">group</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">MyDumbAwareAction</span><span class="o">(</span><span class="s">"Login3"</span><span class="o">));</span> <span class="nc">ActionToolbar</span> <span class="n">toolbar</span> <span class="o">=</span> <span class="nc">ActionManager</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">createActionToolbar</span><span class="o">(</span><span class="s">"ToolBar"</span><span class="o">,</span> <span class="n">group</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> <span class="n">setToolbar</span><span class="o">(</span><span class="n">toolbar</span><span class="o">.</span><span class="na">getComponent</span><span class="o">());</span> <span class="o">}</span> <span class="o">}</span> <span class="c1">// 添加一个页</span> <span class="n">toolWindow</span><span class="o">.</span><span class="na">getContentManager</span><span class="o">().</span><span class="na">addContent</span><span class="o">(</span><span class="nc">ContentFactory</span><span class="o">.</span><span class="na">SERVICE</span><span class="o">.</span><span class="na">getInstance</span><span class="o">().</span><span class="na">createContent</span><span class="o">(</span><span class="k">new</span> <span class="nc">MyPanel</span><span class="o">(</span><span class="kc">false</span><span class="o">),</span> <span class="s">"First"</span><span class="o">,</span> <span class="kc">false</span><span class="o">),</span> <span class="mi">0</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>在 IDEA 的最下面窗体中,如果想展示自己的窗体,则需要开发对应的 ToolWindowFactory 实现类,这样才可以展示你的内容。</li> <li>这里的思想基本是 Swing 技术的开发方式,如果你不熟悉 Swing 最这块内容会比较陌生。</li> </ul> <h3 id="4-pluginxml">4. plugin.xml</h3> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">extensions</span> <span class="n">defaultExtensionNs</span><span class="o">=</span><span class="s">"com.intellij"</span><span class="o">&gt;</span> <span class="o">&lt;!--</span> <span class="nc">Add</span> <span class="n">your</span> <span class="n">extensions</span> <span class="n">here</span> <span class="o">--&gt;</span> <span class="o">&lt;</span><span class="n">toolWindow</span> <span class="n">canCloseContents</span><span class="o">=</span><span class="s">"true"</span> <span class="n">anchor</span><span class="o">=</span><span class="s">"bottom"</span> <span class="n">id</span><span class="o">=</span><span class="s">"SmartIM"</span> <span class="n">factoryClass</span><span class="o">=</span><span class="s">"MyToolWindowFactory"</span><span class="o">&gt;</span> <span class="o">&lt;/</span><span class="n">toolWindow</span><span class="o">&gt;</span> <span class="o">&lt;!--</span> <span class="n">在Setting中添加自定义配置模版</span> <span class="o">--&gt;</span> <span class="o">&lt;</span><span class="n">projectConfigurable</span> <span class="n">groupId</span><span class="o">=</span><span class="s">"Other Settings"</span> <span class="n">displayName</span><span class="o">=</span><span class="s">"My Config"</span> <span class="n">id</span><span class="o">=</span><span class="s">"thief.id"</span> <span class="n">instance</span><span class="o">=</span><span class="s">"MySearchableConfigurable"</span><span class="o">/&gt;</span> <span class="o">&lt;/</span><span class="n">extensions</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="n">actions</span><span class="o">&gt;</span> <span class="o">&lt;!--</span> <span class="nc">Add</span> <span class="n">your</span> <span class="n">actions</span> <span class="n">here</span> <span class="o">--&gt;</span> <span class="o">&lt;</span><span class="n">action</span> <span class="n">id</span><span class="o">=</span><span class="s">"HiId_FileMenu"</span> <span class="kd">class</span><span class="err">="</span><span class="nc">HiClazz</span><span class="s">" text="</span><span class="nc">HiName</span><span class="s">"&gt; &lt;add-to-group group-id="</span><span class="nc">FileMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">MainMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">EditMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">ViewMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">CodeMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">AnalyzeMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">RefactoringMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">BuildMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">RunMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">ToolsMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">WindowMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;add-to-group group-id="</span><span class="nc">HelpMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;/action&gt; &lt;action id="</span><span class="n">HiId_EditorPopupMenu</span><span class="s">" class="</span><span class="nc">HiClazz</span><span class="s">" text="</span><span class="nc">HiName</span><span class="s">"&gt; &lt;add-to-group group-id="</span><span class="nc">EditorPopupMenu</span><span class="s">" anchor="</span><span class="n">first</span><span class="s">"/&gt; &lt;/action&gt; &lt;action id="</span><span class="n">HiId_MainToolBar</span><span class="s">" class="</span><span class="nc">HiClazz</span><span class="s">" text="</span><span class="nc">HiName</span><span class="s">"&gt; &lt;add-to-group group-id="</span><span class="nc">MainToolBar</span><span class="s">" anchor="</span><span class="n">first</span><span class="err">"</span><span class="o">/&gt;</span> <span class="o">&lt;/</span><span class="n">action</span><span class="o">&gt;</span> <span class="o">&lt;/</span><span class="n">actions</span><span class="o">&gt;</span> </code></pre></div></div> <ul> <li>在 plugin.xml 的配置中,主要是把各个功能实现窗体配置到对应的菜单下,比如 Tools 下、toolWindow 里等。</li> </ul> <h3 id="5--测试结果">5. 测试结果</h3> <p><strong>启动运行</strong></p> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-4.png" alt="" /></p> <ul> <li>IDEA 插件开发运行会基于 Plugin 或者 Gradle 下配置的 <code class="highlighter-rouge">::runIde</code></li> </ul> <p><strong>运行界面</strong></p> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-5.png" alt="" /></p> <ul> <li>在 IDEA 的各个菜单中都可以看到新增加的 HiName 插件,在你实际开发的时候选择需要的内容进行配置即可。</li> </ul> <p><strong>运行效果</strong></p> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-6.png" alt="" /></p> <ul> <li>当鼠标点到类的上,在点 HiName 就可以看到对应的工程类信息了。</li> </ul> <h2 id="六插件开发能做啥都">六、插件开发能做啥都</h2> <p>在 GitHub 上搜索 IDEA 插件开发,一共有44页内容,<a href="https://github.com/search?p=41&amp;q=idea%E6%8F%92%E4%BB%B6&amp;type=Repositories">https://github.com/search?p=41&amp;q=idea%E6%8F%92%E4%BB%B6&amp;type=Repositories</a> 涉及到自动化测试、工程脚手架、API生成、生成数据库的DAO类、一些常用工具,当然还有一些比较有意思的,比如:摸鱼看书、听郭德纲相声、微信聊天、局域网聊天、英语翻译等等。这里我给大家列举几个,开阔开阔思路。</p> <h3 id="1-快速生成-crud-工程代码">1. 快速生成 CRUD 工程代码</h3> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-7.png" alt="" /></p> <ul> <li><strong>地址</strong>:<a href="https://github.com/mars05/crud-intellij-plugin">https://github.com/mars05/crud-intellij-plugin</a></li> <li><strong>描述</strong>:一个增删改查的idea插件,可以根据数据库表结构,帮助您快速生成model、dao、service、controller等相关代码。同时支持MyBatis、JPA、MybatisPlus。</li> </ul> <h3 id="2-在-idea-中摸鱼聊天">2. 在 IDEA 中摸鱼聊天</h3> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-8.png" alt="" /></p> <ul> <li><strong>地址</strong>:<a href="https://github.com/Jamling/SmartIM4IntelliJ">https://github.com/Jamling/SmartIM4IntelliJ</a></li> <li><strong>描述</strong>:ntelliJ IDEA上的SmartIM(原SmartQQ)插件,可以在IDEA中使用QQ或微信聊天。安装成功后,会在底部栏出现一个SmartIM的tab(如果没有底部栏,则在菜单View中把ToolButtons勾选上)</li> </ul> <h3 id="3-可视化流程编排">3. 可视化流程编排</h3> <p><img src="https://bugstack.cn/assets/images/middleware/middleware-5-9.png" alt="" /></p> <ul> <li><strong>地址</strong>:<a href="https://github.com/alibaba/compileflow">https://github.com/alibaba/compileflow</a></li> <li><strong>描述</strong>:<code class="highlighter-rouge">compileflow Process</code>引擎是淘宝工作流<code class="highlighter-rouge">TBBPM</code>引擎之一,是专注于纯内存执行,无状态的流程引擎,通过将流程文件转换生成<code class="highlighter-rouge">java</code>代码编译执行,简洁高效。当前是阿里业务中台交易等多个核心系统的流程引擎。在阿里巴巴中台解决方案中广泛使用,支撑了导购、交易、履约、资金等多个业务场景。</li> </ul> <h2 id="七总结">七、总结</h2> <ul> <li>IDEA 开发技术涉及到了对 IDEA 插件开发 API 的熟悉以及UI界面的开发,所以如果想开发一款 IDEA 插件,基本离不开对 Swing 的编写,不过也不需要太复杂的页面,所有这部分技能还好。</li> <li>IDEA 官网文档仅提供了两种构建 IDEA 插件工程的方法,但更推荐 Gradle 方式,这样可以满足你对后续其他功能组件的便捷引入,以及做其他内容的扩展。</li> <li>IDEA 插件开发可以开发出很多用于提效研发编程的技术插件,例如一些监控、脚手架、接口API以及调试、流程化低代码编排等等,所以这部分内容的价值还是蛮大的。</li> </ul> <h2 id="八系列推荐">八、系列推荐</h2> <ul> <li><a href="https://bugstack.cn/framework/2021/02/04/%E5%9F%BA%E4%BA%8EIDEA%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91%E5%92%8C%E5%AD%97%E8%8A%82%E7%A0%81%E6%8F%92%E6%A1%A9%E6%8A%80%E6%9C%AF-%E5%AE%9E%E7%8E%B0%E7%A0%94%E5%8F%91%E4%BA%A4%E4%BB%98%E8%B4%A8%E9%87%8F%E8%87%AA%E5%8A%A8%E5%88%86%E6%9E%90.html">基于IDEA插件开发和字节码插桩技术,实现研发交付质量自动分析</a></li> <li><a href="https://bugstack.cn/framework/2021/02/21/%E5%85%B3%E4%BA%8E%E4%BD%8E%E4%BB%A3%E7%A0%81%E7%BC%96%E7%A8%8B%E7%9A%84%E5%8F%AF%E6%8C%81%E7%BB%AD%E6%80%A7%E4%BA%A4%E4%BB%98%E8%AE%BE%E8%AE%A1%E5%92%8C%E5%88%86%E6%9E%90.html">关于低代码编程的可持续性交付设计和分析</a></li> <li><a href="https://bugstack.cn/framework/2021/03/14/%E4%B8%8D%E9%87%8D%E5%A4%8D%E9%80%A0%E8%BD%AE%E5%AD%90%E5%8F%AA%E6%98%AF%E9%AA%97%E5%B0%8F%E5%AD%A9%E5%AD%90%E7%9A%84-%E6%95%99%E4%BD%A0%E6%89%8B%E6%92%B8-SpringBoot-%E8%84%9A%E6%89%8B%E6%9E%B6.html">不重复造轮子都是骗小孩的,教你手撸 SpringBoot 脚手架!</a></li> <li><a href="https://bugstack.cn/framework/2021/07/19/%E8%B0%83%E7%A0%94%E5%AD%97%E8%8A%82%E7%A0%81%E6%8F%92%E6%A1%A9%E6%8A%80%E6%9C%AF-%E7%94%A8%E4%BA%8E%E7%B3%BB%E7%BB%9F%E7%9B%91%E6%8E%A7%E8%AE%BE%E8%AE%A1%E5%92%8C%E5%AE%9E%E7%8E%B0.html">调研字节码插桩技术,用于系统监控设计和实现</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2021/05/26/%E5%B0%8F%E5%82%85%E5%93%A5-%E4%B8%80%E4%B8%AA%E6%9C%89-%E5%89%AF%E4%B8%9A-%E7%9A%84%E7%A0%81%E5%86%9C.html">小傅哥,一个有“副业”的码农!</a></li> </ul> Fri, 27 Aug 2021 00:00:00 +0800 https://bugstack.cn/itstack-ark-middleware/2021/08/27/%E6%8A%80%E6%9C%AF%E8%B0%83%E7%A0%94-IDEA-%E6%8F%92%E4%BB%B6%E6%80%8E%E4%B9%88%E5%BC%80%E5%8F%91.html https://bugstack.cn/itstack-ark-middleware/2021/08/27/%E6%8A%80%E6%9C%AF%E8%B0%83%E7%A0%94-IDEA-%E6%8F%92%E4%BB%B6%E6%80%8E%E4%B9%88%E5%BC%80%E5%8F%91.html java itstack-ark-middleware itstack-ark-middleware 带头撸项目,《DDD + RPC 开发分布式架构,抽奖系统》 <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a> <br />原文:<a href="https://mp.weixin.qq.com/s/VthCUlT8oAJqKOoq5_NzSQ">https://mp.weixin.qq.com/s/VthCUlT8oAJqKOoq5_NzSQ</a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一咋撸个项目">一、咋,撸个项目?</h2> <p><strong>总有粉丝伙伴问傅哥</strong>,有没有能<code class="highlighter-rouge">上手练习技术的项目</code>,现在学了这么多技术知识、看了这么多设计模式、搜了这么多架构设计,但这些内容都是怎么结合在一起使用的呢?互联网中的项目架构设计是什么样的呢?我该怎么开始学到什么样才能进大厂呢?</p> <table> <thead> <tr> <th>项目学习意见(收集结果)</th> </tr> </thead> <tbody> <tr> <td><img src="https://bugstack.cn/assets/images/framework/framework-7-01.png" alt="图 1-1" /></td> </tr> </tbody> </table> <ul> <li><a href="https://docs.qq.com/sheet/DT1VKZmpaeEtFYWVB">项目学习意见(收集结果).xlsx</a></li> </ul> <p><code class="highlighter-rouge">咋neng呢,撸个项目吧!</code></p> <p>在撸项目开始之前,做了一次项目学习意见调研,问了问大家:“想做个什么项目,如;积分商城、抽奖系统、活动系统、监控系统、技术组件,并且这些项目中用到了哪些技术栈。”</p> <p>最后在大家的意见反馈中得到结果是,要先以开发互联网中C端类项目 <strong>抽奖系统</strong> 开始,这样一个项目可以让大家在系统的<code class="highlighter-rouge">架构搭建</code>、<code class="highlighter-rouge">功能配置</code>、<code class="highlighter-rouge">服务开发</code>中学习到关于一些关于解决<code class="highlighter-rouge">高并发</code>、<code class="highlighter-rouge">高性能</code>、<code class="highlighter-rouge">高可用</code>场景时的技术实践运用。<em>放心,其他类的互联网项目,我们也会陆续的折腾起来!</em></p> <p>So!基于DDD领域驱动设计的四层架构<strong>抽奖系统</strong>,开始啦!有座,这趟车的你跟上!</p> <h2 id="二呀都能学啥">二、呀,都能学啥?</h2> <p><img src="https://bugstack.cn/assets/images/framework/framework-7-02.png" alt="图 1-2" /></p> <p>一个以真实场景<code class="highlighter-rouge">实践技术栈整合</code>开发实际需求的项目,势必会因为要完成需求而引入各项技术栈的使用,也会由于要解决互联网中C端场景中的三高问题,而使用相应的技术实现不同类别解决与方案,我们也可以把此类解决方案理解为DDD中的业务领域模型开发。在这个设计和开发的过程中会涉及到<code class="highlighter-rouge">架构设计</code>、<code class="highlighter-rouge">技术应用</code>、<code class="highlighter-rouge">场景实现</code>,每一块内容都会有非常多的实践知识,可以让读者学到东西。</p> <h2 id="三嘿让我看看">三、嘿,让我看看!</h2> <h3 id="1-目录章节">1. 目录章节</h3> <p>一项代码实战派的傅哥,已经在两个周末的时间折腾出不少内容了,包括:从系统框架的搭建、广播模式Dubbo的配置、库表的设计以及部分功能的实现等,接下来在大家<strong>上车</strong>后,就可以对着已经完成的内容学习和跟进新内容的实现了。<em>DDD四层系统架构测试案例如图:</em></p> <p>当然完成的内容远不止上面截图的框架搭建,还包括下列章节:</p> <ul class="task-list"> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" /><a href="#">第 01 节:开篇介绍</a></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" /><a href="#">第 02 节:搭建(DDD + RPC)架构</a></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" /><a href="#">第 03 节:跑通广播模式RPC过程调用</a></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" /><a href="#">第 04 节:抽奖活动策略库表设计</a></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" checked="checked" /><a href="#">第 05 节:抽奖策略领域模块开发</a></li> <li class="task-list-item"><input type="checkbox" class="task-list-item-checkbox" disabled="disabled" /><a href="#">第 06 节:待归档</a></li> </ul> <p><em>提醒:文章需要代码库授权后可见,每周末和假期更新进度,以及不定期安排<code class="highlighter-rouge">视频直播</code>讲解共性学习问题。</em></p> <h3 id="2-代码仓库">2. 代码仓库</h3> <p><img src="https://bugstack.cn/assets/images/framework/framework-7-04.png" alt="" /></p> <h3 id="3-工程结构">3. 工程结构</h3> <p><img src="https://bugstack.cn/assets/images/framework/framework-7-03.png" alt="" /></p> <h3 id="4-运行结果">4. 运行结果</h3> <p><img src="https://bugstack.cn/assets/images/framework/framework-7-05.png" alt="" /></p> <h2 id="四来上傅哥车">四、来,上傅哥车!</h2> <p>🚌 <code class="highlighter-rouge">来吧,上车,还有座!</code></p> <p><strong>【小提醒】</strong>:这是一个付费项目,加入小傅哥的知识星球<code class="highlighter-rouge">码农会锁</code>,就可以参与到项目实战学习中了。<em>当然,知识星球远不止这一个项目,后续还会有其他项目,另外你还会获得知识星球专属用户的一些特殊学习照顾</em>。</p> <p><strong>【优惠券】</strong>:在<strong>公众号:bugstack虫洞栈</strong>,回复:<code class="highlighter-rouge">加入星球</code>,你会获得一张<code class="highlighter-rouge">粉丝专属折扣券</code>,冲!</p> <p><img src="https://bugstack.cn/assets/images/illustration/zsxq.jpeg" alt="" /></p> <p><strong>代码授权</strong>:加入星球后,你会在星球里看到一个<strong>加入项目</strong>的🔝置顶消息,进入后按照步骤操作后,就可以被授权开通<code class="highlighter-rouge">《抽奖系统的代码库》</code>权限了。</p> <p><strong>学习说明</strong>:为了能让读者伙伴快速🔜进入项目学习,可以按照下面的步骤开始,<code class="highlighter-rouge">在项目中会有更详细说明</code>:</p> <ol> <li>【入口】<a href="https://codechina.csdn.net/KnowledgePlanet/Lottery">Lottery <em>仅代码授权后可访问</em></a> 项目主入口中有一个 <code class="highlighter-rouge">README.md</code> 有关于项目的学习说明、开发规范、章节目录和问题交流提交issue说明以及群内交流,在学习的过程中可以参考使用。</li> <li>【文章】每一个章节内容中都会包括;需求、实现、验证、细节,四块内容的介绍,以及当前章节中对应的代码分支可以切换学习。</li> <li>【代码】在代码学习的过程中可以克隆工程进行开发练习,也可以给主工程小傅哥工程代码<code class="highlighter-rouge">提交PR</code>、<code class="highlighter-rouge">ISSUE</code>,我会去审核和合并以及不断的完善代码。</li> </ol> <h2 id="五好总结一下">五、好,总结一下!</h2> <p>动手、动手、动手,一个实践类型的项目最需要的就是你动起手来,只有这样你才能发现各种问题细节的处理。更何况哪怕在别人电脑💻上运行的再顺畅的代码,在你那也可能<em>拉跨</em>,不过没关系因为所有拉跨的过程都将是你抓住学习的点!</p> <p>我一直坚持很多事情要慢下来,希望你也不要过于的着急快,火急火燎不是学习的长久过程,而迟迟以恒才能让你的收获更加丰满。趁着时间还多正当年恰,坚持做好自己想做的事情吧!</p> Sun, 22 Aug 2021 00:00:00 +0800 https://bugstack.cn/framework/2021/08/22/%E5%B8%A6%E5%A4%B4%E6%92%B8%E9%A1%B9%E7%9B%AE-DDD-+-RPC-%E5%BC%80%E5%8F%91%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84-%E6%8A%BD%E5%A5%96%E7%B3%BB%E7%BB%9F.html https://bugstack.cn/framework/2021/08/22/%E5%B8%A6%E5%A4%B4%E6%92%B8%E9%A1%B9%E7%9B%AE-DDD-+-RPC-%E5%BC%80%E5%8F%91%E5%88%86%E5%B8%83%E5%BC%8F%E6%9E%B6%E6%9E%84-%E6%8A%BD%E5%A5%96%E7%B3%BB%E7%BB%9F.html java framework framework 基于Hash散列,数据库路由组件设计 <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一前言">一、前言</h2> <p><code class="highlighter-rouge">什么?Java 面试就像造火箭🚀</code></p> <p><img src="https://bugstack.cn/assets/images/middleware/blog-4-0.png" alt="" /></p> <p><strong>单纯了!</strong> 以前我也一直想 Java 面试就好好面试呗,<em>嘎哈么</em>总考一些工作中也用不到的玩意,会用 <code class="highlighter-rouge">Spring</code>、<code class="highlighter-rouge">MyBatis</code>、<code class="highlighter-rouge">Dubbo</code>、<code class="highlighter-rouge">MQ</code>,把业务需求实现了不就行了!</p> <p>但当工作几年后,需要提升自己(<del>要加钱</del>)的时候,竟然开始觉得自己只是一个调用 API 攒接口的工具人。没有知识宽度,没有技术纵深,也想不出来更没有意识,把日常开发的业务代码中通用的共性逻辑提炼出来,开发成公用的组件,更没有去思考日常使用的一些组件是用什么技术实现的。</p> <p>所以有时候你说面试好像就是在造火箭,这些技术日常根本用不到,其实很多时候不是这个技术用不到,而是因为你没用(<em>嗯,以前我也没用</em>)。当你有这个想法想突破自己的薪资待遇瓶颈时,就需要去<code class="highlighter-rouge">了解了解必备的数据结构</code>、<code class="highlighter-rouge">学习学习Java的算法逻辑</code>、<code class="highlighter-rouge">熟悉熟悉通用的设计模式</code>、再结合像 Spring、ORM、RPC,这样的源码实现逻辑,把相应的技术方案赋能到自己的日常业务开发中,把共性的问题用聚焦和提炼的方式进行解决,这些才是你在 CRUD 之外的能力体现(<del>加薪筹码</del>)。</p> <p><strong>怎么?</strong> 好像听上去有道理,那么举个<em>栗子</em>,来一场<code class="highlighter-rouge">数据库路由</code>的需求分析和逻辑实现!</p> <h2 id="二需求分析">二、需求分析</h2> <p><code class="highlighter-rouge">如果要做一个数据库路由,都需要做什么技术点?</code></p> <p>首先我们要知道为什么要用分库分表,其实就是由于业务体量较大,数据增长较快,所以需要把用户数据拆分到不同的库表中去,减轻数据库压力。</p> <p>分库分表操作主要有垂直拆分和水平拆分:</p> <ul> <li>垂直拆分:指按照业务将表进行分类,分布到不同的数据库上,这样也就将数据的压力分担到不同的库上面。最终一个数据库由很多表的构成,每个表对应着不同的业务,也就是专库专用。</li> <li>水平拆分:如果垂直拆分后遇到单机瓶颈,可以使用水平拆分。相对于垂直拆分的区别是:垂直拆分是把不同的表拆到不同的数据库中,而水平拆分是把同一个表拆到不同的数据库中。如:user_001、user_002</li> </ul> <p>而本章节我们要实现的也是水平拆分的路由设计,如图 1-1</p> <p><img src="https://bugstack.cn/assets/images/middleware/blog-4-1.png" alt="图 1-1" /></p> <p>那么,这样的一个数据库路由设计要包括哪些技术知识点呢?</p> <ul> <li>是关于 AOP 切面拦截的使用,这是因为需要给使用数据库路由的方法做上标记,便于处理分库分表逻辑。</li> <li>数据源的切换操作,既然有分库那么就会涉及在多个数据源间进行链接切换,以便把数据分配给不同的数据库。</li> <li>数据库表寻址操作,一条数据分配到哪个数据库,哪张表,都需要进行索引计算。在方法调用的过程中最终通过 ThreadLocal 记录。</li> <li>为了能让数据均匀的分配到不同的库表中去,还需要考虑如何进行数据散列的操作,不能分库分表后,让数据都集中在某个库的某个表,这样就失去了分库分表的意义。</li> </ul> <p>综上,可以看到在数据库和表的数据结构下完成数据存放,我需要用到的技术包括:<code class="highlighter-rouge">AOP</code>、<code class="highlighter-rouge">数据源切换</code>、<code class="highlighter-rouge">散列算法</code>、<code class="highlighter-rouge">哈希寻址</code>、<code class="highlighter-rouge">ThreadLoca</code>l以及<code class="highlighter-rouge">SpringBoot的Starter开发方式</code>等技术。而像<code class="highlighter-rouge">哈希散列</code>、<code class="highlighter-rouge">寻址</code>、<code class="highlighter-rouge">数据存放</code>,其实这样的技术与 HashMap 有太多相似之处,<strong>那么学完源码造火箭的机会来了</strong> 如果你有过深入分析和学习过 HashMap 源码、Spring 源码、中间件开发,那么在设计这样的数据库路由组件时一定会有很多思路的出来。<em>接下来我们一起尝试下从源码学习到造火箭!</em></p> <h2 id="三技术调研">三、技术调研</h2> <p>在 JDK 源码中,包含的数据结构设计有:数组、链表、队列、栈、红黑树,具体的实现有 ArrayList、LinkedList、Queue、Stack,而这些在数据存放都是顺序存储,并没有用到哈希索引的方式进行处理。而 HashMap、ThreadLocal,两个功能则用了哈希索引、散列算法以及在数据膨胀时候的拉链寻址和开放寻址,所以我们要分析和借鉴的也会集中在这两个功能上。</p> <h3 id="1-threadlocal">1. ThreadLocal</h3> <p><img src="https://bugstack.cn/assets/images/middleware/blog-4-2.png" alt="" /></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Test</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">test_idx</span><span class="o">()</span> <span class="o">{</span> <span class="kt">int</span> <span class="n">hashCode</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="k">for</span> <span class="o">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> <span class="n">i</span> <span class="o">&lt;</span> <span class="mi">16</span><span class="o">;</span> <span class="n">i</span><span class="o">++)</span> <span class="o">{</span> <span class="n">hashCode</span> <span class="o">=</span> <span class="n">i</span> <span class="o">*</span> <span class="mh">0x61c88647</span> <span class="o">+</span> <span class="mh">0x61c88647</span><span class="o">;</span> <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="n">hashCode</span> <span class="o">&amp;</span> <span class="mi">15</span><span class="o">;</span> <span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"斐波那契散列:"</span> <span class="o">+</span> <span class="n">idx</span> <span class="o">+</span> <span class="s">" 普通散列:"</span> <span class="o">+</span> <span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">i</span><span class="o">).</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">&amp;</span> <span class="mi">15</span><span class="o">));</span> <span class="o">}</span> <span class="o">}</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">7</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">0</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">14</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">1</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">5</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">2</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">12</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">3</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">3</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">4</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">10</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">5</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">1</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">6</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">8</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">7</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">15</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">8</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">6</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">9</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">13</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">15</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">4</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">0</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">11</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">1</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">2</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">2</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">9</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">3</span> <span class="n">斐波那契散列</span><span class="err">:</span><span class="mi">0</span> <span class="n">普通散列</span><span class="err">:</span><span class="mi">4</span> </code></pre></div></div> <ul> <li><strong>数据结构</strong>:散列表的数组结构</li> <li><strong>散列算法</strong>:斐波那契(Fibonacci)散列法</li> <li><strong>寻址方式</strong>:Fibonacci 散列法可以让数据更加分散,在发生数据碰撞时进行开放寻址,从碰撞节点向后寻找位置进行存放元素。公式:<code class="highlighter-rouge">f(k) = ((k * 2654435769) &gt;&gt; X) &lt;&lt; Y对于常见的32位整数而言,也就是 f(k) = (k * 2654435769) &gt;&gt; 28 </code>,黄金分割点:<code class="highlighter-rouge">(√5 - 1) / 2 = 0.6180339887</code> <code class="highlighter-rouge">1.618:1 == 1:0.618</code></li> <li><strong>学到什么</strong>:可以参考寻址方式和散列算法,但这种数据结构与要设计实现作用到数据库上的结构相差较大,不过 ThreadLocal 可以用于存放和传递数据索引信息。</li> </ul> <h3 id="2-hashmap">2. HashMap</h3> <p><img src="https://bugstack.cn/assets/images/middleware/blog-4-3.png" alt="" /></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">static</span> <span class="kt">int</span> <span class="nf">disturbHashIdx</span><span class="o">(</span><span class="nc">String</span> <span class="n">key</span><span class="o">,</span> <span class="kt">int</span> <span class="n">size</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="o">(</span><span class="n">size</span> <span class="o">-</span> <span class="mi">1</span><span class="o">)</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">^</span> <span class="o">(</span><span class="n">key</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">&gt;&gt;&gt;</span> <span class="mi">16</span><span class="o">));</span> <span class="o">}</span> </code></pre></div></div> <ul> <li><strong>数据结构</strong>:哈希桶数组 + 链表 + 红黑树</li> <li><strong>散列算法</strong>:扰动函数、哈希索引,可以让数据更加散列的分布</li> <li><strong>寻址方式</strong>:通过拉链寻址的方式解决数据碰撞,数据存放时会进行索引地址,遇到碰撞产生数据链表,在一定容量超过8个元素进行扩容或者树化。</li> <li><strong>学到什么</strong>:可以把散列算法、寻址方式都运用到数据库路由的设计实现中,还有整个数组+链表的方式其实库+表的方式也有类似之处。</li> </ul> <h2 id="四设计实现">四、设计实现</h2> <h3 id="1-定义路由注解">1. 定义路由注解</h3> <p><strong>定义</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Documented</span> <span class="nd">@Retention</span><span class="o">(</span><span class="nc">RetentionPolicy</span><span class="o">.</span><span class="na">RUNTIME</span><span class="o">)</span> <span class="nd">@Target</span><span class="o">({</span><span class="nc">ElementType</span><span class="o">.</span><span class="na">TYPE</span><span class="o">,</span> <span class="nc">ElementType</span><span class="o">.</span><span class="na">METHOD</span><span class="o">})</span> <span class="kd">public</span> <span class="nd">@interface</span> <span class="nc">DBRouter</span> <span class="o">{</span> <span class="nc">String</span> <span class="nf">key</span><span class="o">()</span> <span class="k">default</span> <span class="s">""</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <p><strong>使用</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Mapper</span> <span class="kd">public</span> <span class="kd">interface</span> <span class="nc">IUserDao</span> <span class="o">{</span> <span class="nd">@DBRouter</span><span class="o">(</span><span class="n">key</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">)</span> <span class="nc">User</span> <span class="nf">queryUserInfoByUserId</span><span class="o">(</span><span class="nc">User</span> <span class="n">req</span><span class="o">);</span> <span class="nd">@DBRouter</span><span class="o">(</span><span class="n">key</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">)</span> <span class="kt">void</span> <span class="nf">insertUser</span><span class="o">(</span><span class="nc">User</span> <span class="n">req</span><span class="o">);</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>首先我们需要自定义一个注解,用于放置在需要被数据库路由的方法上。</li> <li>它的使用方式是通过方法配置注解,就可以被我们指定的 AOP 切面进行拦截,拦截后进行相应的数据库路由计算和判断,并切换到相应的操作数据源上。</li> </ul> <h3 id="2-解析路由配置">2. 解析路由配置</h3> <p><img src="https://bugstack.cn/assets/images/middleware/blog-4-4.png" alt="" /></p> <ul> <li>以上就是我们实现完数据库路由组件后的一个数据源配置,在分库分表下的数据源使用中,都需要支持多数据源的信息配置,这样才能满足不同需求的扩展。</li> <li>对于这种自定义较大的信息配置,就需要使用到 <code class="highlighter-rouge">org.springframework.context.EnvironmentAware</code> 接口,来获取配置文件并提取需要的配置信息。</li> </ul> <p><strong>数据源配置提取</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setEnvironment</span><span class="o">(</span><span class="nc">Environment</span> <span class="n">environment</span><span class="o">)</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">prefix</span> <span class="o">=</span> <span class="s">"router.jdbc.datasource."</span><span class="o">;</span> <span class="n">dbCount</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">environment</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">prefix</span> <span class="o">+</span> <span class="s">"dbCount"</span><span class="o">));</span> <span class="n">tbCount</span> <span class="o">=</span> <span class="nc">Integer</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">environment</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">prefix</span> <span class="o">+</span> <span class="s">"tbCount"</span><span class="o">));</span> <span class="nc">String</span> <span class="n">dataSources</span> <span class="o">=</span> <span class="n">environment</span><span class="o">.</span><span class="na">getProperty</span><span class="o">(</span><span class="n">prefix</span> <span class="o">+</span> <span class="s">"list"</span><span class="o">);</span> <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">dbInfo</span> <span class="o">:</span> <span class="n">dataSources</span><span class="o">.</span><span class="na">split</span><span class="o">(</span><span class="s">","</span><span class="o">))</span> <span class="o">{</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">dataSourceProps</span> <span class="o">=</span> <span class="nc">PropertyUtil</span><span class="o">.</span><span class="na">handle</span><span class="o">(</span><span class="n">environment</span><span class="o">,</span> <span class="n">prefix</span> <span class="o">+</span> <span class="n">dbInfo</span><span class="o">,</span> <span class="nc">Map</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">dataSourceMap</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">dbInfo</span><span class="o">,</span> <span class="n">dataSourceProps</span><span class="o">);</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>prefix,是数据源配置的开头信息,你可以自定义需要的开头内容。</li> <li>dbCount、tbCount、dataSources、dataSourceProps,都是对配置信息的提取,并存放到 dataSourceMap 中便于后续使用。</li> </ul> <h3 id="3-数据源切换">3. 数据源切换</h3> <p>在结合 SpringBoot 开发的 Starter 中,需要提供一个 DataSource 的实例化对象,那么这个对象我们就放在 DataSourceAutoConfig 来实现,并且这里提供的数据源是可以动态变换的,也就是支持动态切换数据源。</p> <p><strong>创建数据源</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Bean</span> <span class="kd">public</span> <span class="nc">DataSource</span> <span class="nf">dataSource</span><span class="o">()</span> <span class="o">{</span> <span class="c1">// 创建数据源</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Object</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">targetDataSources</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">HashMap</span><span class="o">&lt;&gt;();</span> <span class="k">for</span> <span class="o">(</span><span class="nc">String</span> <span class="n">dbInfo</span> <span class="o">:</span> <span class="n">dataSourceMap</span><span class="o">.</span><span class="na">keySet</span><span class="o">())</span> <span class="o">{</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">String</span><span class="o">,</span> <span class="nc">Object</span><span class="o">&gt;</span> <span class="n">objMap</span> <span class="o">=</span> <span class="n">dataSourceMap</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">dbInfo</span><span class="o">);</span> <span class="n">targetDataSources</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">dbInfo</span><span class="o">,</span> <span class="k">new</span> <span class="nc">DriverManagerDataSource</span><span class="o">(</span><span class="n">objMap</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"url"</span><span class="o">).</span><span class="na">toString</span><span class="o">(),</span> <span class="n">objMap</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"username"</span><span class="o">).</span><span class="na">toString</span><span class="o">(),</span> <span class="n">objMap</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="s">"password"</span><span class="o">).</span><span class="na">toString</span><span class="o">()));</span> <span class="o">}</span> <span class="c1">// 设置数据源</span> <span class="nc">DynamicDataSource</span> <span class="n">dynamicDataSource</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">DynamicDataSource</span><span class="o">();</span> <span class="n">dynamicDataSource</span><span class="o">.</span><span class="na">setTargetDataSources</span><span class="o">(</span><span class="n">targetDataSources</span><span class="o">);</span> <span class="k">return</span> <span class="n">dynamicDataSource</span><span class="o">;</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>这里是一个简化的创建案例,把基于从配置信息中读取到的数据源信息,进行实例化创建。</li> <li>数据源创建完成后存放到 <code class="highlighter-rouge">DynamicDataSource</code> 中,它是一个继承了 AbstractRoutingDataSource 的实现类,这个类里可以存放和读取相应的具体调用的数据源信息。</li> </ul> <h3 id="4-切面拦截">4. 切面拦截</h3> <p>在 AOP 的切面拦截中需要完成;数据库路由计算、扰动函数加强散列、计算库表索引、设置到 ThreadLocal 传递数据源,整体案例代码如下:</p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Around</span><span class="o">(</span><span class="s">"aopPoint() &amp;&amp; @annotation(dbRouter)"</span><span class="o">)</span> <span class="kd">public</span> <span class="nc">Object</span> <span class="nf">doRouter</span><span class="o">(</span><span class="nc">ProceedingJoinPoint</span> <span class="n">jp</span><span class="o">,</span> <span class="nc">DBRouter</span> <span class="n">dbRouter</span><span class="o">)</span> <span class="kd">throws</span> <span class="nc">Throwable</span> <span class="o">{</span> <span class="nc">String</span> <span class="n">dbKey</span> <span class="o">=</span> <span class="n">dbRouter</span><span class="o">.</span><span class="na">key</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="nc">StringUtils</span><span class="o">.</span><span class="na">isBlank</span><span class="o">(</span><span class="n">dbKey</span><span class="o">))</span> <span class="k">throw</span> <span class="k">new</span> <span class="nc">RuntimeException</span><span class="o">(</span><span class="s">"annotation DBRouter key is null!"</span><span class="o">);</span> <span class="c1">// 计算路由</span> <span class="nc">String</span> <span class="n">dbKeyAttr</span> <span class="o">=</span> <span class="n">getAttrValue</span><span class="o">(</span><span class="n">dbKey</span><span class="o">,</span> <span class="n">jp</span><span class="o">.</span><span class="na">getArgs</span><span class="o">());</span> <span class="kt">int</span> <span class="n">size</span> <span class="o">=</span> <span class="n">dbRouterConfig</span><span class="o">.</span><span class="na">getDbCount</span><span class="o">()</span> <span class="o">*</span> <span class="n">dbRouterConfig</span><span class="o">.</span><span class="na">getTbCount</span><span class="o">();</span> <span class="c1">// 扰动函数</span> <span class="kt">int</span> <span class="n">idx</span> <span class="o">=</span> <span class="o">(</span><span class="n">size</span> <span class="o">-</span> <span class="mi">1</span><span class="o">)</span> <span class="o">&amp;</span> <span class="o">(</span><span class="n">dbKeyAttr</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">^</span> <span class="o">(</span><span class="n">dbKeyAttr</span><span class="o">.</span><span class="na">hashCode</span><span class="o">()</span> <span class="o">&gt;&gt;&gt;</span> <span class="mi">16</span><span class="o">));</span> <span class="c1">// 库表索引</span> <span class="kt">int</span> <span class="n">dbIdx</span> <span class="o">=</span> <span class="n">idx</span> <span class="o">/</span> <span class="n">dbRouterConfig</span><span class="o">.</span><span class="na">getTbCount</span><span class="o">()</span> <span class="o">+</span> <span class="mi">1</span><span class="o">;</span> <span class="kt">int</span> <span class="n">tbIdx</span> <span class="o">=</span> <span class="n">idx</span> <span class="o">-</span> <span class="n">dbRouterConfig</span><span class="o">.</span><span class="na">getTbCount</span><span class="o">()</span> <span class="o">*</span> <span class="o">(</span><span class="n">dbIdx</span> <span class="o">-</span> <span class="mi">1</span><span class="o">);</span> <span class="c1">// 设置到 ThreadLocal</span> <span class="nc">DBContextHolder</span><span class="o">.</span><span class="na">setDBKey</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%02d"</span><span class="o">,</span> <span class="n">dbIdx</span><span class="o">));</span> <span class="nc">DBContextHolder</span><span class="o">.</span><span class="na">setTBKey</span><span class="o">(</span><span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"%02d"</span><span class="o">,</span> <span class="n">tbIdx</span><span class="o">));</span> <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"数据库路由 method:{} dbIdx:{} tbIdx:{}"</span><span class="o">,</span> <span class="n">getMethod</span><span class="o">(</span><span class="n">jp</span><span class="o">).</span><span class="na">getName</span><span class="o">(),</span> <span class="n">dbIdx</span><span class="o">,</span> <span class="n">tbIdx</span><span class="o">);</span> <span class="c1">// 返回结果</span> <span class="k">try</span> <span class="o">{</span> <span class="k">return</span> <span class="n">jp</span><span class="o">.</span><span class="na">proceed</span><span class="o">();</span> <span class="o">}</span> <span class="k">finally</span> <span class="o">{</span> <span class="nc">DBContextHolder</span><span class="o">.</span><span class="na">clearDBKey</span><span class="o">();</span> <span class="nc">DBContextHolder</span><span class="o">.</span><span class="na">clearTBKey</span><span class="o">();</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <ul> <li>简化的核心逻辑实现代码如上,首先我们提取了库表乘积的数量,把它当成 HashMap 一样的长度进行使用。</li> <li>接下来使用和 HashMap 一样的扰动函数逻辑,让数据分散的更加散列。</li> <li>当计算完总长度上的一个索引位置后,还需要把这个位置折算到库表中,看看总体长度的索引因为落到哪个库哪个表。</li> <li>最后是把这个计算的索引信息存放到 ThreadLocal 中,用于传递在方法调用过程中可以提取到索引信息。</li> </ul> <h3 id="5-测试验证">5. 测试验证</h3> <h4 id="51-库表创建">5.1 库表创建</h4> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">create</span> <span class="n">database</span> <span class="err">`</span><span class="n">bugstack_01</span><span class="err">`</span><span class="o">;</span> <span class="no">DROP</span> <span class="no">TABLE</span> <span class="n">user_01</span><span class="o">;</span> <span class="no">CREATE</span> <span class="no">TABLE</span> <span class="nf">user_01</span> <span class="o">(</span> <span class="n">id</span> <span class="n">bigint</span> <span class="no">NOT</span> <span class="no">NULL</span> <span class="no">AUTO_INCREMENT</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">自增ID</span><span class="err">'</span><span class="o">,</span> <span class="n">userId</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">9</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户ID</span><span class="err">'</span><span class="o">,</span> <span class="n">userNickName</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">32</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户昵称</span><span class="err">'</span><span class="o">,</span> <span class="n">userHead</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">16</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户头像</span><span class="err">'</span><span class="o">,</span> <span class="n">userPassword</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">64</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户密码</span><span class="err">'</span><span class="o">,</span> <span class="n">createTime</span> <span class="n">datetime</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">创建时间</span><span class="err">'</span><span class="o">,</span> <span class="n">updateTime</span> <span class="n">datetime</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">更新时间</span><span class="err">'</span><span class="o">,</span> <span class="no">PRIMARY</span> <span class="nf">KEY</span> <span class="o">(</span><span class="n">id</span><span class="o">)</span> <span class="o">)</span> <span class="no">ENGINE</span><span class="o">=</span><span class="nc">InnoDB</span> <span class="no">DEFAULT</span> <span class="no">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="o">;</span> <span class="no">DROP</span> <span class="no">TABLE</span> <span class="n">user_02</span><span class="o">;</span> <span class="no">CREATE</span> <span class="no">TABLE</span> <span class="nf">user_02</span> <span class="o">(</span> <span class="n">id</span> <span class="n">bigint</span> <span class="no">NOT</span> <span class="no">NULL</span> <span class="no">AUTO_INCREMENT</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">自增ID</span><span class="err">'</span><span class="o">,</span> <span class="n">userId</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">9</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户ID</span><span class="err">'</span><span class="o">,</span> <span class="n">userNickName</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">32</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户昵称</span><span class="err">'</span><span class="o">,</span> <span class="n">userHead</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">16</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户头像</span><span class="err">'</span><span class="o">,</span> <span class="n">userPassword</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">64</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户密码</span><span class="err">'</span><span class="o">,</span> <span class="n">createTime</span> <span class="n">datetime</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">创建时间</span><span class="err">'</span><span class="o">,</span> <span class="n">updateTime</span> <span class="n">datetime</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">更新时间</span><span class="err">'</span><span class="o">,</span> <span class="no">PRIMARY</span> <span class="nf">KEY</span> <span class="o">(</span><span class="n">id</span><span class="o">)</span> <span class="o">)</span> <span class="no">ENGINE</span><span class="o">=</span><span class="nc">InnoDB</span> <span class="no">DEFAULT</span> <span class="no">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="o">;</span> <span class="no">DROP</span> <span class="no">TABLE</span> <span class="n">user_03</span><span class="o">;</span> <span class="no">CREATE</span> <span class="no">TABLE</span> <span class="nf">user_03</span> <span class="o">(</span> <span class="n">id</span> <span class="n">bigint</span> <span class="no">NOT</span> <span class="no">NULL</span> <span class="no">AUTO_INCREMENT</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">自增ID</span><span class="err">'</span><span class="o">,</span> <span class="n">userId</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">9</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户ID</span><span class="err">'</span><span class="o">,</span> <span class="n">userNickName</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">32</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户昵称</span><span class="err">'</span><span class="o">,</span> <span class="n">userHead</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">16</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户头像</span><span class="err">'</span><span class="o">,</span> <span class="n">userPassword</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">64</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户密码</span><span class="err">'</span><span class="o">,</span> <span class="n">createTime</span> <span class="n">datetime</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">创建时间</span><span class="err">'</span><span class="o">,</span> <span class="n">updateTime</span> <span class="n">datetime</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">更新时间</span><span class="err">'</span><span class="o">,</span> <span class="no">PRIMARY</span> <span class="nf">KEY</span> <span class="o">(</span><span class="n">id</span><span class="o">)</span> <span class="o">)</span> <span class="no">ENGINE</span><span class="o">=</span><span class="nc">InnoDB</span> <span class="no">DEFAULT</span> <span class="no">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="o">;</span> <span class="no">DROP</span> <span class="no">TABLE</span> <span class="n">user_04</span><span class="o">;</span> <span class="no">CREATE</span> <span class="no">TABLE</span> <span class="nf">user_04</span> <span class="o">(</span> <span class="n">id</span> <span class="n">bigint</span> <span class="no">NOT</span> <span class="no">NULL</span> <span class="no">AUTO_INCREMENT</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">自增ID</span><span class="err">'</span><span class="o">,</span> <span class="n">userId</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">9</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户ID</span><span class="err">'</span><span class="o">,</span> <span class="n">userNickName</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">32</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户昵称</span><span class="err">'</span><span class="o">,</span> <span class="n">userHead</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">16</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户头像</span><span class="err">'</span><span class="o">,</span> <span class="n">userPassword</span> <span class="nf">varchar</span><span class="o">(</span><span class="mi">64</span><span class="o">)</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">用户密码</span><span class="err">'</span><span class="o">,</span> <span class="n">createTime</span> <span class="n">datetime</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">创建时间</span><span class="err">'</span><span class="o">,</span> <span class="n">updateTime</span> <span class="n">datetime</span> <span class="no">COMMENT</span> <span class="err">'</span><span class="n">更新时间</span><span class="err">'</span><span class="o">,</span> <span class="no">PRIMARY</span> <span class="nf">KEY</span> <span class="o">(</span><span class="n">id</span><span class="o">)</span> <span class="o">)</span> <span class="no">ENGINE</span><span class="o">=</span><span class="nc">InnoDB</span> <span class="no">DEFAULT</span> <span class="no">CHARSET</span><span class="o">=</span><span class="n">utf8</span><span class="o">;</span> </code></pre></div></div> <ul> <li>创建相同表结构的多个库存信息,bugstack_01、bugstack_02</li> </ul> <h4 id="52-语句配置">5.2 语句配置</h4> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&lt;</span><span class="n">select</span> <span class="n">id</span><span class="o">=</span><span class="s">"queryUserInfoByUserId"</span> <span class="n">parameterType</span><span class="o">=</span><span class="s">"cn.bugstack.middleware.test.infrastructure.po.User"</span> <span class="n">resultType</span><span class="o">=</span><span class="s">"cn.bugstack.middleware.test.infrastructure.po.User"</span><span class="o">&gt;</span> <span class="no">SELECT</span> <span class="n">id</span><span class="o">,</span> <span class="n">userId</span><span class="o">,</span> <span class="n">userNickName</span><span class="o">,</span> <span class="n">userHead</span><span class="o">,</span> <span class="n">userPassword</span><span class="o">,</span> <span class="n">createTime</span> <span class="no">FROM</span> <span class="n">user_</span><span class="err">$</span><span class="o">{</span><span class="n">tbIdx</span><span class="o">}</span> <span class="n">where</span> <span class="n">userId</span> <span class="o">=</span> <span class="err">#</span><span class="o">{</span><span class="n">userId</span><span class="o">}</span> <span class="o">&lt;/</span><span class="n">select</span><span class="o">&gt;</span> <span class="o">&lt;</span><span class="n">insert</span> <span class="n">id</span><span class="o">=</span><span class="s">"insertUser"</span> <span class="n">parameterType</span><span class="o">=</span><span class="s">"cn.bugstack.middleware.test.infrastructure.po.User"</span><span class="o">&gt;</span> <span class="n">insert</span> <span class="n">into</span> <span class="n">user_</span><span class="err">$</span><span class="o">{</span><span class="n">tbIdx</span><span class="o">}</span> <span class="o">(</span><span class="n">id</span><span class="o">,</span> <span class="n">userId</span><span class="o">,</span> <span class="n">userNickName</span><span class="o">,</span> <span class="n">userHead</span><span class="o">,</span> <span class="n">userPassword</span><span class="o">,</span><span class="n">createTime</span><span class="o">,</span> <span class="n">updateTime</span><span class="o">)</span> <span class="n">values</span> <span class="o">(</span><span class="err">#</span><span class="o">{</span><span class="n">id</span><span class="o">},</span><span class="err">#</span><span class="o">{</span><span class="n">userId</span><span class="o">},</span><span class="err">#</span><span class="o">{</span><span class="n">userNickName</span><span class="o">},</span><span class="err">#</span><span class="o">{</span><span class="n">userHead</span><span class="o">},</span><span class="err">#</span><span class="o">{</span><span class="n">userPassword</span><span class="o">},</span><span class="n">now</span><span class="o">(),</span><span class="n">now</span><span class="o">())</span> <span class="o">&lt;/</span><span class="n">insert</span><span class="o">&gt;</span> </code></pre></div></div> <ul> <li>在 MyBatis 的语句使用上,唯一变化的需要在表名后面添加一个占位符,<code class="highlighter-rouge">${tbIdx}</code> 用于写入当前的表ID。</li> </ul> <h4 id="53-注解配置">5.3 注解配置</h4> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@DBRouter</span><span class="o">(</span><span class="n">key</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">)</span> <span class="nc">User</span> <span class="nf">queryUserInfoByUserId</span><span class="o">(</span><span class="nc">User</span> <span class="n">req</span><span class="o">);</span> <span class="nd">@DBRouter</span><span class="o">(</span><span class="n">key</span> <span class="o">=</span> <span class="s">"userId"</span><span class="o">)</span> <span class="kt">void</span> <span class="nf">insertUser</span><span class="o">(</span><span class="nc">User</span> <span class="n">req</span><span class="o">);</span> </code></pre></div></div> <ul> <li>在需要使用分库分表的方法上添加注解,添加注解后这个方法就会被 AOP 切面管理。</li> </ul> <h4 id="54-单元测试">5.4 单元测试</h4> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="mi">22</span><span class="o">:</span><span class="mi">38</span><span class="o">:</span><span class="mf">20.067</span> <span class="no">INFO</span> <span class="mi">19900</span> <span class="o">---</span> <span class="o">[</span> <span class="n">main</span><span class="o">]</span> <span class="n">c</span><span class="o">.</span><span class="na">b</span><span class="o">.</span><span class="na">m</span><span class="o">.</span><span class="na">db</span><span class="o">.</span><span class="na">router</span><span class="o">.</span><span class="na">DBRouterJoinPoint</span> <span class="o">:</span> <span class="n">数据库路由</span> <span class="n">method</span><span class="err">:</span><span class="n">queryUserInfoByUserId</span> <span class="n">dbIdx</span><span class="err">:</span><span class="mi">2</span> <span class="n">tbIdx</span><span class="err">:</span><span class="mi">3</span> <span class="mi">22</span><span class="o">:</span><span class="mi">38</span><span class="o">:</span><span class="mf">20.594</span> <span class="no">INFO</span> <span class="mi">19900</span> <span class="o">---</span> <span class="o">[</span> <span class="n">main</span><span class="o">]</span> <span class="n">cn</span><span class="o">.</span><span class="na">bugstack</span><span class="o">.</span><span class="na">middleware</span><span class="o">.</span><span class="na">test</span><span class="o">.</span><span class="na">ApiTest</span> <span class="o">:</span> <span class="n">测试结果</span><span class="err">:</span><span class="o">{</span><span class="s">"createTime"</span><span class="o">:</span><span class="mi">1615908803000</span><span class="o">,</span><span class="s">"id"</span><span class="o">:</span><span class="mi">2</span><span class="o">,</span><span class="s">"userHead"</span><span class="o">:</span><span class="s">"01_50"</span><span class="o">,</span><span class="s">"userId"</span><span class="o">:</span><span class="s">"980765512"</span><span class="o">,</span><span class="s">"userNickName"</span><span class="o">:</span><span class="s">"小傅哥"</span><span class="o">,</span><span class="s">"userPassword"</span><span class="o">:</span><span class="s">"123456"</span><span class="o">}</span> <span class="mi">22</span><span class="o">:</span><span class="mi">38</span><span class="o">:</span><span class="mf">20.620</span> <span class="no">INFO</span> <span class="mi">19900</span> <span class="o">---</span> <span class="o">[</span><span class="n">extShutdownHook</span><span class="o">]</span> <span class="n">o</span><span class="o">.</span><span class="na">s</span><span class="o">.</span><span class="na">s</span><span class="o">.</span><span class="na">concurrent</span><span class="o">.</span><span class="na">ThreadPoolTaskExecutor</span> <span class="o">:</span> <span class="nc">Shutting</span> <span class="n">down</span> <span class="nc">ExecutorService</span> <span class="err">'</span><span class="n">applicationTaskExecutor</span><span class="err">'</span><span class="mi">1</span> </code></pre></div></div> <ul> <li>以上就是我们使用自己的数据库路由组件执行时的一个日志信息,可以看到这里包含了路由操作,在2库3表:<code class="highlighter-rouge">数据库路由 method:queryUserInfoByUserId dbIdx:2 tbIdx:3</code></li> </ul> <h2 id="五总结">五、总结</h2> <p><img src="https://bugstack.cn/assets/images/middleware/blog-4-5.png" alt="" /></p> <p><strong>综上</strong> 就是我们从 HashMap、ThreadLocal、Spring等源码学习中了解到技术内在原理,并把这样的技术用在一个数据库路由设计上。如果没有经历过这些总被说成<em>造火箭</em>的技术沉淀,那么几乎也不太可能顺利开发出一个这样一个中间件,所有很多时候根本不是技术没用,而是自己没用上没机会用而已。不要总惦记那一片片重复的 CRUD,看看还有哪些知识是真的可以提升个人能力的!参考资料:<a href="https://juejin.cn/book/6940996508632219689">《SpringBoot 中间件设计和开发》</a></p> <h2 id="六系列推荐">六、系列推荐</h2> <ul> <li><a href="https://bugstack.cn/spring/2021/08/12/%E6%89%8B%E6%92%B8-Spring-PDF-%E5%85%A8%E4%B9%A6260%E9%A1%B56.5%E4%B8%87%E5%AD%97-%E5%AE%8C%E7%A8%BF&amp;%E5%8F%91%E7%89%88.html">《手撸 Spring》PDF,全书260页6.5万字,整理分享</a></li> <li><a href="https://bugstack.cn/itstack-ark-middleware/2019/12/02/Spring-Boot-%E4%B8%AD%E9%97%B4%E4%BB%B6%E5%BC%80%E5%8F%91(%E4%B8%80)-%E6%9C%8D%E5%8A%A1%E6%B2%BB%E7%90%86%E4%B8%AD%E9%97%B4%E4%BB%B6%E4%B9%8B%E7%BB%9F%E4%B8%80%E7%99%BD%E5%90%8D%E5%8D%95%E9%AA%8C%E8%AF%81.html">服务治理中间件之统一白名单验证</a></li> <li><a href="https://bugstack.cn/itstack-demo-netty-3/2021/08/17/%E7%BB%99%E5%AD%A6%E4%B9%A0%E5%8A%A0%E7%82%B9%E5%AE%9E%E8%B7%B5-%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E5%88%86%E5%B8%83%E5%BC%8FIM(%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1)%E7%B3%BB%E7%BB%9F.html">开发一个分布式IM(即时通信)系统!</a></li> <li><a href="https://bugstack.cn/interview/2021/05/05/%E9%9D%A2%E7%BB%8F%E6%89%8B%E5%86%8C-%E7%AC%AC31%E7%AF%87-Spring-Bean-IOC-AOP-%E5%BE%AA%E7%8E%AF%E4%BE%9D%E8%B5%96%E8%A7%A3%E8%AF%BB.html">Spring Bean IOC、AOP 循环依赖解读</a></li> <li><a href="https://bugstack.cn/itstack-code-life/2021/05/09/%E5%A4%A7%E5%AD%A6%E6%AF%95%E4%B8%9A%E8%A6%81%E5%86%99%E5%A4%9A%E5%B0%91%E8%A1%8C%E4%BB%A3%E7%A0%81-%E6%89%8D%E8%83%BD%E4%B8%8D%E7%94%A8%E8%8A%B1%E9%92%B1%E5%9F%B9%E8%AE%AD%E5%B0%B1%E6%89%BE%E5%88%B0%E4%B8%80%E4%BB%BD%E5%BC%80%E5%8F%91%E5%B7%A5%E4%BD%9C.html">毕业前写了20万行代码,让我从成为同学眼里的面霸!</a></li> </ul> Thu, 19 Aug 2021 00:00:00 +0800 https://bugstack.cn/itstack-ark-middleware/2021/08/19/%E5%9F%BA%E4%BA%8EHash%E6%95%A3%E5%88%97-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%B7%AF%E7%94%B1%E7%BB%84%E4%BB%B6%E8%AE%BE%E8%AE%A1.html https://bugstack.cn/itstack-ark-middleware/2021/08/19/%E5%9F%BA%E4%BA%8EHash%E6%95%A3%E5%88%97-%E6%95%B0%E6%8D%AE%E5%BA%93%E8%B7%AF%E7%94%B1%E7%BB%84%E4%BB%B6%E8%AE%BE%E8%AE%A1.html java itstack-ark-middleware itstack-ark-middleware 给学习加点实践,开发一个分布式IM(即时通信)系统! <p>作者:小傅哥 <br />博客:<a href="https://bugstack.cn">https://bugstack.cn</a></p> <blockquote> <p>沉淀、分享、成长,让自己和他人都能有所收获!😄</p> </blockquote> <h2 id="一前言">一、前言</h2> <p><code class="highlighter-rouge">这知识学的,根本没有忘的快呀?!</code></p> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-1.png" alt="" /></p> <p>是不是感觉很多资料,<code class="highlighter-rouge">点收藏起来爽</code>、<code class="highlighter-rouge">看视频时候嗨</code>、<code class="highlighter-rouge">读文章当时会</code>,只要过了那个劲,就完了,根本不记得这里面都讲了啥。时间浪费了,东西还没学到手,这是为啥?</p> <p>其实因为学习也分为上策、中策和下策:</p> <ul> <li>下策:眼睛看就行,坐着、窝着、躺着,都行,反正也不累,还能一边回复下吹水的微信群</li> <li>中策:看完的资料做笔记整理归纳,长期积累资料</li> <li>上策:实践、上手、应用、调试、归纳、整理资料,总结经验输出文档</li> </ul> <p>综上,下策学起来很快感觉自己好像会了不少,中策有点要动手了懒不想动,上策就很耗时耗力了要自己对每一个知识点都能事必躬亲到亲力亲为。就这样你在学习的时候不自觉的就选择了<strong>下策</strong>,因此其实并没有学到什么。</p> <p>学习能把知识学到手,讲究的是实践,在小傅哥编写的文章中,基本都是以实践代码验证结果为核心,讲述文章内容。<em>😁从小我就喜欢动手</em>,就以一个即时通信的项目为例,已经基于不同技术方案实现了5、6次,仅为了<strong>实践技术</strong>,截图如下:</p> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-2.png" alt="" /></p> <ul> <li>有些是刚学完Socket和Swing的时候,想动手试试这些技术能不能写个QQ出来。</li> <li>也有的是因为实习培训需要完成的项目,不过在有了一些基础后,一周时间就能写完全部功能。</li> <li>虽然这些项目在现在看上去还是丑丑的界面,以及代码逻辑可能也不是那么完善。但放在学习阶段的每一次实现中,都能为自己带来很多技术上的成长。</li> </ul> <hr /> <p>那么,这次IM实践的机会给你,希望你能用的上!接下来我会给你介绍一个IM的系统架构、通信协议、单聊群聊、表情发送、UI事件驱动等各项内容,以及提供全套的源码让你可以上手学习。</p> <h2 id="二演示">二、演示</h2> <p>在开始学习之前,先给大家演示下这套<strong>仿照PC端微信界面的IM系统</strong>运行效果。</p> <p><strong>聊天页面</strong></p> <p><img src="https://bugstack.cn/assets/images/2020/ui-01.png" alt="" /></p> <p><strong>添加好友</strong></p> <p><img src="https://bugstack.cn/assets/images/2020/ui-02.png" alt="" /></p> <p><strong>视频演示</strong></p> <p><a href="https://www.bilibili.com/video/BV1BZ4y1W7fC">https://www.bilibili.com/video/BV1BZ4y1W7fC</a></p> <h2 id="三系统设计">三、系统设计</h2> <p>在这套<code class="highlighter-rouge">IM</code>中,服务端采用<code class="highlighter-rouge">DDD</code>领域驱动设计模式进行搭建。将 Netty 的功能交给 <code class="highlighter-rouge">SpringBoot</code> 进行启停控制,同时在服务端搭建控制台可以非常方便的操作通信系统,进行用户和通信管理。在客户端的建设上采用<code class="highlighter-rouge">UI</code>分离的方式进行搭建,以保证业务代码与<code class="highlighter-rouge">UI</code>展示分离,做到非常易于扩展的控制。</p> <p>另外在功能实现上包括;完美仿照微信桌面版客户端、登录、搜索添加好友、用户通信、群组通信、表情发送等核心功能。如果有对于实际需要使用的功能,可以按照这套系统框架进行扩展。</p> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-3.png" alt="" /></p> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-4.png" alt="" /></p> <ul> <li><strong>UI开发</strong>:使用<code class="highlighter-rouge">JavaFx</code>与<code class="highlighter-rouge">Maven</code>搭建UI桌面工程,逐步讲解登录框体、聊天框体、对话框、好友栏等各项UI展示及操作事件。从而在这一章节中让Java 程序员学会开发桌面版应用。</li> <li><strong>架构设计</strong>:在这一章节中我们会使用DDD领域驱动设计的四层模型结构与Netty结合使用,架构出合理的分层框架。同时还有相应库表功能的设计。相信这些内容学习后,你一定也可以假设出更好的框架。</li> <li><strong>功能实现</strong>:这部分我们主要将通信中的各项功能逐步实现,包括;登录、添加好友、对话通知、消息发送、断线重连等各项功能。最终完成整个项目的开发,同时也可以让你从实践中学会技能。</li> </ul> <h2 id="四ui开发">四、UI开发</h2> <h3 id="1-整体结构定义侧边栏">1. 整体结构定义、侧边栏</h3> <p>聊天窗体,相对于登陆窗体来说,聊天窗体的内容会比较多,同时也会相对复杂一些。因此我们会分章节的逐步来实现这些窗体以及事件和接口功能。在本篇文章中我们会主要讲解聊天框体的搭建以及侧边栏 UI 开发。</p> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-5.png" alt="" /></p> <ul> <li>首先是我们整个聊天主窗体的定义,是一块空白面板,并去掉默认的边框按钮 (最小化、退出等)</li> <li>之后是我们左侧边栏,我们称之为条形 Bar,功能区域的实现。</li> <li>最后添加窗体事件,当点击按钮时变换 <code class="highlighter-rouge">内容面板</code> 中的填充信息。</li> </ul> <h3 id="2-对话聊天框">2. 对话聊天框</h3> <p>对话框选中后的内容区域展现,也就是用户之间信息发送和展现。从整体上看这是一个联动的过程,点击左侧的对话框用户,右侧就有相应内容的填充。那么右侧被填充对话列表 ListView 需要与每一个对话用户关联,点击聊天用户的时候,是通过反复切换填充的过程。</p> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-6.png" alt="" /></p> <ul> <li>点击左侧的每一个对话框体,右侧聊天框填充内容即随之变化。同时还有相应的对话名称也会也变化。</li> <li>对话框中左侧展示好友发送的信息,右侧展示个人发送的信息。同时消息内容会随着内容的增多而增加高度和宽度。</li> <li>最下面是文本输入框,在后面的实现里我们文本输入框采用公用的方式进行设计,当然你也可以设计为单独的个人使用。</li> </ul> <h3 id="3-好友栏">3. 好友栏</h3> <p>大家都经常使用 PC 端的微信,可以知道在好友栏里是分了几段内容的,其中包含;新的朋友、公众号、群组和最下面的好友。</p> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-7.png" alt="" /></p> <ul> <li>最上面的搜索框这部分内容不变,和前面的一样。我们目前使用的方式是 fxml 设计,例如这部分是通用功能,可以抽取出来放到代码中,设计成一个组件元素类。</li> <li>经过我们的分析,在使用 JavaFx 组件开发为基础下,这部分是一种嵌套 ListView,也就是最底层的面板是一个 ListView,好友和群组有各是一个 ListView,这样处理后我们会很方便的进行数据填充。</li> <li>另外这样的结构主要有利于在我们程序运行过程中,如果你添加了好友,那么我们需要将好友信息刷新到好友栏中,而在数据填充的时候,为了更加便捷高效,所以我们设计了嵌套的 ListView。如果还不是特别理解,可以从后续的代码中获得答案。</li> </ul> <h3 id="4-事件定义">4. 事件定义</h3> <p>在桌面版 UI 开发中,为了能使 UI 与业务逻辑隔离,需要在我们把 UI 打包后提供出操作界面的展示效果的接口以及界面操作事件抽象类。那么可以按照下图理解;</p> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-8.png" alt="" /></p> <table> <thead> <tr> <th>序号</th> <th>接口名</th> <th>描述</th> </tr> </thead> <tbody> <tr> <td>1</td> <td>void doShow()</td> <td>打开窗口</td> </tr> <tr> <td>2</td> <td>void setUserInfo(String userId, String userNickName, String userHead)</td> <td>设置登陆用户 ID、昵称、头像</td> </tr> <tr> <td>3</td> <td>void addTalkBox(int talkIdx, Integer talkType, String talkId, String talkName, String talkHead, String talkSketch, Date talkDate, Boolean selected)</td> <td>填充对话框列表</td> </tr> <tr> <td>4</td> <td>void addTalkMsgUserLeft(String talkId, String msg, Date msgData, Boolean idxFirst, Boolean selected, Boolean isRemind)</td> <td>填充对话框消息 - 好友 (别人的消息)</td> </tr> </tbody> </table> <ul> <li>以上这些接口就是我们目前 UI 为外部提供的所有行为接口,这些接口的一个链路描述就是;打开窗口、搜索好友、添加好友、打开对话框、发送消息。</li> </ul> <h2 id="五通信设计">五、通信设计</h2> <h3 id="1-系统架构">1. 系统架构</h3> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-9.png" alt="" /></p> <p>在前面我们说到更适合的架构,才是符合你当下需要最好的架构。那么怎么设计这样架构呢,基本就是要找到符合点的目标。我们之所以这样设计是为什么,那么在这个系统里有如下几点;</p> <ul> <li>我们系统在服务端要有 web 页面进行管理通信用户以及服务端的控制和监控。</li> <li>数据库的对象类,不要被外部污染,要有隔离性。比如说;你的数据库类暴漏给外部做展示类使用了,那么现在需要增加一个字段,而这个字段又不是你数据库存在的属性。那么这个时候就已经把数据库类污染了。</li> <li>因为目前我们都是在 Java 语言下实现 Netty 通信,那么服务端与客户端都会需要使用到通信过程中的协议定义和解析。那么我们需要抽离这一层对外提供 Jar 包。</li> <li>接口、业务处理、底层服务、通信交互,要有明确的区分和实现,避免造成混乱难以维护。</li> </ul> <p>结合我们上面这四点的目标,你头脑中有什么模型结构体现了呢?以及相应的技术栈选择上是否有计划了?接下来我们会介绍两种架构设计的模型,一种是你非常熟悉的 <code class="highlighter-rouge">MVC</code>,另外一种是你可能听说过的 <code class="highlighter-rouge">DDD</code> 领域驱动设计。</p> <h3 id="2-通信协议">2. 通信协议</h3> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-10.png" alt="" /></p> <p>从图稿上来看,我们在传输对象的时候需要在传输包中添加一个 <strong>帧标识</strong> 以此来判断当前的业务对象是哪个对象,也就可以让我们的业务更加清晰,避免使用大量的 if 语句判断。</p> <p><strong>协议框架</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">agreement</span> <span class="err">└──</span> <span class="n">src</span> <span class="err">├──</span> <span class="n">main</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">org</span><span class="o">.</span><span class="na">itstack</span><span class="o">.</span><span class="na">naive</span><span class="o">.</span><span class="na">chat</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">codec</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">ObjDecoder</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="nc">ObjEncoder</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">protocol</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">demo</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">├──</span> <span class="nc">Command</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="nc">Packet</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">util</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="nc">SerializationUtil</span><span class="o">.</span><span class="na">java</span> <span class="err">│</span> <span class="err">├──</span> <span class="n">resources</span> <span class="err">│</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">application</span><span class="o">.</span><span class="na">yml</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">webapp</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">chat</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">res</span> <span class="err">│</span> <span class="err">└──</span> <span class="n">index</span><span class="o">.</span><span class="na">html</span> <span class="err">└──</span> <span class="n">test</span> <span class="err">└──</span> <span class="n">java</span> <span class="err">└──</span> <span class="n">org</span><span class="o">.</span><span class="na">itstack</span><span class="o">.</span><span class="na">demo</span><span class="o">.</span><span class="na">test</span> <span class="err">└──</span> <span class="nc">ApiTest</span><span class="o">.</span><span class="na">java</span> </code></pre></div></div> <p><strong>协议包</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">abstract</span> <span class="kd">class</span> <span class="nc">Packet</span> <span class="o">{</span> <span class="kd">private</span> <span class="kd">final</span> <span class="kd">static</span> <span class="nc">Map</span><span class="o">&lt;</span><span class="nc">Byte</span><span class="o">,</span> <span class="nc">Class</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="nc">Packet</span><span class="o">&gt;&gt;</span> <span class="n">packetType</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ConcurrentHashMap</span><span class="o">&lt;&gt;();</span> <span class="kd">static</span> <span class="o">{</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">LoginRequest</span><span class="o">,</span> <span class="nc">LoginRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">LoginResponse</span><span class="o">,</span> <span class="nc">LoginResponse</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">MsgRequest</span><span class="o">,</span> <span class="nc">MsgRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">MsgResponse</span><span class="o">,</span> <span class="nc">MsgResponse</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">TalkNoticeRequest</span><span class="o">,</span> <span class="nc">TalkNoticeRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">TalkNoticeResponse</span><span class="o">,</span> <span class="nc">TalkNoticeResponse</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">SearchFriendRequest</span><span class="o">,</span> <span class="nc">SearchFriendRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">SearchFriendResponse</span><span class="o">,</span> <span class="nc">SearchFriendResponse</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">AddFriendRequest</span><span class="o">,</span> <span class="nc">AddFriendRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">AddFriendResponse</span><span class="o">,</span> <span class="nc">AddFriendResponse</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">DelTalkRequest</span><span class="o">,</span> <span class="nc">DelTalkRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">MsgGroupRequest</span><span class="o">,</span> <span class="nc">MsgGroupRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">MsgGroupResponse</span><span class="o">,</span> <span class="nc">MsgGroupResponse</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="n">packetType</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="nc">Command</span><span class="o">.</span><span class="na">ReconnectRequest</span><span class="o">,</span> <span class="nc">ReconnectRequest</span><span class="o">.</span><span class="na">class</span><span class="o">);</span> <span class="o">}</span> <span class="kd">public</span> <span class="kd">static</span> <span class="nc">Class</span><span class="o">&lt;?</span> <span class="kd">extends</span> <span class="nc">Packet</span><span class="o">&gt;</span> <span class="nf">get</span><span class="o">(</span><span class="nc">Byte</span> <span class="n">command</span><span class="o">)</span> <span class="o">{</span> <span class="k">return</span> <span class="n">packetType</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">command</span><span class="o">);</span> <span class="o">}</span> <span class="cm">/** * 获取协议指令 * * @return 返回指令值 */</span> <span class="kd">public</span> <span class="kd">abstract</span> <span class="nc">Byte</span> <span class="nf">getCommand</span><span class="o">();</span> <span class="o">}</span> </code></pre></div></div> <h3 id="3-添加好友">3. 添加好友</h3> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-11.png" alt="" /></p> <ul> <li>从上面的流程中可以看到,这里包含了两部分内容;(1) 搜索好友,(2) 添加好友。当天就完成好友后,好友会出现到我们的好友栏中。</li> <li>并且这里面我们采用的是单方面同意加好友,也就是你添加一个好友的时候,对方也同样有你的好友信息。</li> <li>如果你的业务中是需要添加好友并同意的,那么可以在发起好友添加的时候,添加一条状态信息,请求加好友。对方同意后,两个用户才能成为好友并进行通信。</li> </ul> <p><strong>添加好友,案例代码</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">AddFriendHandler</span> <span class="kd">extends</span> <span class="nc">MyBizHandler</span><span class="o">&lt;</span><span class="nc">AddFriendRequest</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kd">public</span> <span class="nf">AddFriendHandler</span><span class="o">(</span><span class="nc">UserService</span> <span class="n">userService</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">(</span><span class="n">userService</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">channelRead</span><span class="o">(</span><span class="nc">Channel</span> <span class="n">channel</span><span class="o">,</span> <span class="nc">AddFriendRequest</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 1. 添加好友到数据库中[A-&gt;B B-&gt;A]</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">UserFriend</span><span class="o">&gt;</span> <span class="n">userFriendList</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">&lt;&gt;();</span> <span class="n">userFriendList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">UserFriend</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getUserId</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getFriendId</span><span class="o">()));</span> <span class="n">userFriendList</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="k">new</span> <span class="nc">UserFriend</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getFriendId</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getUserId</span><span class="o">()));</span> <span class="n">userService</span><span class="o">.</span><span class="na">addUserFriend</span><span class="o">(</span><span class="n">userFriendList</span><span class="o">);</span> <span class="c1">// 2. 推送好友添加完成 A</span> <span class="nc">UserInfo</span> <span class="n">userInfo</span> <span class="o">=</span> <span class="n">userService</span><span class="o">.</span><span class="na">queryUserInfo</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getFriendId</span><span class="o">());</span> <span class="n">channel</span><span class="o">.</span><span class="na">writeAndFlush</span><span class="o">(</span><span class="k">new</span> <span class="nc">AddFriendResponse</span><span class="o">(</span><span class="n">userInfo</span><span class="o">.</span><span class="na">getUserId</span><span class="o">(),</span> <span class="n">userInfo</span><span class="o">.</span><span class="na">getUserNickName</span><span class="o">(),</span> <span class="n">userInfo</span><span class="o">.</span><span class="na">getUserHead</span><span class="o">()));</span> <span class="c1">// 3. 推送好友添加完成 B</span> <span class="nc">Channel</span> <span class="n">friendChannel</span> <span class="o">=</span> <span class="nc">SocketChannelUtil</span><span class="o">.</span><span class="na">getChannel</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getFriendId</span><span class="o">());</span> <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="n">friendChannel</span><span class="o">)</span> <span class="k">return</span><span class="o">;</span> <span class="nc">UserInfo</span> <span class="n">friendInfo</span> <span class="o">=</span> <span class="n">userService</span><span class="o">.</span><span class="na">queryUserInfo</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getUserId</span><span class="o">());</span> <span class="n">friendChannel</span><span class="o">.</span><span class="na">writeAndFlush</span><span class="o">(</span><span class="k">new</span> <span class="nc">AddFriendResponse</span><span class="o">(</span><span class="n">friendInfo</span><span class="o">.</span><span class="na">getUserId</span><span class="o">(),</span> <span class="n">friendInfo</span><span class="o">.</span><span class="na">getUserNickName</span><span class="o">(),</span> <span class="n">friendInfo</span><span class="o">.</span><span class="na">getUserHead</span><span class="o">()));</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <h3 id="4-消息应答">4. 消息应答</h3> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-12.png" alt="" /></p> <ul> <li>从整体的流程可以看到,在用户发起好友、群组通信的时候,会触发一个事件行为,接下来客户端向服务端发送与好友的对话请求。</li> <li>服务端收到对话请求后,如果是好友对话,那么需要保存与好友的通信信息到对话框中。同时通知好友,我与你要通信了。你在自己的对话框列表中,把我加进去。</li> <li>那么如果是群组通信,是可以不用这样通知的,因为不可能把还没有在线的所有群组用户全部通知(人家还没登录呢),所以这部分只需要在用户上线收到信息后,创建出对话框到列表中即可。可以仔细理解下,同时也可以想想其他实现的方式。</li> </ul> <p><strong>消息应答,案例代码</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">MsgHandler</span> <span class="kd">extends</span> <span class="nc">MyBizHandler</span><span class="o">&lt;</span><span class="nc">MsgRequest</span><span class="o">&gt;</span> <span class="o">{</span> <span class="kd">public</span> <span class="nf">MsgHandler</span><span class="o">(</span><span class="nc">UserService</span> <span class="n">userService</span><span class="o">)</span> <span class="o">{</span> <span class="kd">super</span><span class="o">(</span><span class="n">userService</span><span class="o">);</span> <span class="o">}</span> <span class="nd">@Override</span> <span class="kd">public</span> <span class="kt">void</span> <span class="nf">channelRead</span><span class="o">(</span><span class="nc">Channel</span> <span class="n">channel</span><span class="o">,</span> <span class="nc">MsgRequest</span> <span class="n">msg</span><span class="o">)</span> <span class="o">{</span> <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"消息信息处理:{}"</span><span class="o">,</span> <span class="no">JSON</span><span class="o">.</span><span class="na">toJSONString</span><span class="o">(</span><span class="n">msg</span><span class="o">));</span> <span class="c1">// 异步写库</span> <span class="n">userService</span><span class="o">.</span><span class="na">asyncAppendChatRecord</span><span class="o">(</span><span class="k">new</span> <span class="nc">ChatRecordInfo</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getUserId</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getFriendId</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getMsgText</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getMsgType</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getMsgDate</span><span class="o">()));</span> <span class="c1">// 添加对话框[如果对方没有你的对话框则添加]</span> <span class="n">userService</span><span class="o">.</span><span class="na">addTalkBoxInfo</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getFriendId</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getUserId</span><span class="o">(),</span> <span class="nc">Constants</span><span class="o">.</span><span class="na">TalkType</span><span class="o">.</span><span class="na">Friend</span><span class="o">.</span><span class="na">getCode</span><span class="o">());</span> <span class="c1">// 获取好友通信管道</span> <span class="nc">Channel</span> <span class="n">friendChannel</span> <span class="o">=</span> <span class="nc">SocketChannelUtil</span><span class="o">.</span><span class="na">getChannel</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getFriendId</span><span class="o">());</span> <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="n">friendChannel</span><span class="o">)</span> <span class="o">{</span> <span class="n">logger</span><span class="o">.</span><span class="na">info</span><span class="o">(</span><span class="s">"用户id:{}未登录!"</span><span class="o">,</span> <span class="n">msg</span><span class="o">.</span><span class="na">getFriendId</span><span class="o">());</span> <span class="k">return</span><span class="o">;</span> <span class="o">}</span> <span class="c1">// 发送消息</span> <span class="n">friendChannel</span><span class="o">.</span><span class="na">writeAndFlush</span><span class="o">(</span><span class="k">new</span> <span class="nc">MsgResponse</span><span class="o">(</span><span class="n">msg</span><span class="o">.</span><span class="na">getUserId</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getMsgText</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getMsgType</span><span class="o">(),</span> <span class="n">msg</span><span class="o">.</span><span class="na">getMsgDate</span><span class="o">()));</span> <span class="o">}</span> <span class="o">}</span> </code></pre></div></div> <h3 id="5-断线重连">5. 断线重连</h3> <p><img src="https://bugstack.cn/assets/images/2020/netty/IM-13.png" alt="" /></p> <ul> <li>从上述流程中我们看到,当网络连接断开以后,会像服务端发送重新链接的请求。 那么在这个发起链接的过程,和系统的最开始链接有所区别。断线重连是需要将用户的 ID 信息一同- - 发送给服务端,好让服务端可以去更新用户与通信管道 Channel 的绑定关系。</li> <li>同时还需要更新群组内的重连信息,把用户的重连加入群组映射中。此时就可以恢复用户与好友和群组的通信功能。</li> </ul> <p><strong>消息应答,案例代码</strong></p> <div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Channel 状态定时巡检;3 秒后每 5 秒执行一次</span> <span class="n">scheduledExecutorService</span><span class="o">.</span><span class="na">scheduleAtFixedRate</span><span class="o">(()</span> <span class="o">-&gt;</span> <span class="o">{</span><span class="k">while</span> <span class="o">(!</span><span class="n">nettyClient</span><span class="o">.</span><span class="na">isActive</span><span class="o">())</span> <span class="o">{</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"通信管道巡检:通信管道状态"</span> <span class="o">+</span> <span class="n">nettyClient</span><span class="o">.</span><span class="na">isActive</span><span class="o">());</span> <span class="k">try</span> <span class="o">{</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"通信管道巡检:断线重连 [Begin]"</span><span class="o">);</span> <span class="nc">Channel</span> <span class="n">freshChannel</span> <span class="o">=</span> <span class="n">executorService</span><span class="o">.</span><span class="na">submit</span><span class="o">(</span><span class="n">nettyClient</span><span class="o">).</span><span class="na">get</span><span class="o">();</span> <span class="k">if</span> <span class="o">(</span><span class="kc">null</span> <span class="o">==</span> <span class="nc">CacheUtil</span><span class="o">.</span><span class="na">userId</span><span class="o">)</span> <span class="k">continue</span><span class="o">;</span> <span class="n">freshChannel</span><span class="o">.</span><span class="na">writeAndFlush</span><span class="o">(</span><span class="k">new</span> <span class="nc">ReconnectRequest</span><span class="o">(</span><span class="nc">CacheUtil</span><span class="o">.</span><span class="na">userId</span><span class="o">));</span> <span class="o">}</span> <span class="k">catch</span> <span class="o">(</span><span class="nc">InterruptedException</span> <span class="o">|</span> <span class="nc">ExecutionException</span> <span class="n">e</span><span class="o">)</span> <span class="o">{</span><span class="nc">System</span><span class="o">.</span><span class="na">out</span><span class="o">.</span><span class="na">println</span><span class="o">(</span><span class="s">"通信管道巡检:断线重连 [Error]"</span><span class="o">);}</span> <span class="o">}</span> <span class="o">},</span> <span class="mi">3</span><span class="o">,</span> <span class="mi">5</span><span class="o">,</span> <span class="nc">TimeUnit</span><span class="o">.</span><span class="na">SECONDS</span><span class="o">);</span> </code></pre></div></div> <h3 id="6-集群通信">6. 集群通信</h3> <p><img src="https://bugstack.cn/assets/images/pic-content/2019/09/netty-2-09-3.png" alt="" /></p> <ul> <li>跨服务之间案例采用redis的发布和订阅进行传递消息,如果你是大型服务可以使用zookeeper</li> <li>用户A在发送消息给用户B时候,需要传递B的channeId,以用于服务端进行查找channeId所属是否自己的服务内</li> <li>单台机器也可以启动多个Netty服务,程序内会自动寻找可用端口</li> </ul> <h2 id="六源码下载">六、源码下载</h2> <p>本项目是作者小傅哥使用JavaFx、Netty4.x、SpringBoot、Mysql等技术栈和偏向于DDD领域驱动设计方式,搭建的仿桌面版微信实现通信核心功能。</p> <p>这套 <code class="highlighter-rouge">IM</code> 代码分为了三组模块;UI、客户端、服务端。之所以这样拆分,是为了将UI展示与业务逻辑隔离,使用事件和接口进行驱动,让代码层次更加干净整洁易于扩展和维护。</p> <table> <thead> <tr> <th style="text-align: left">序号</th> <th style="text-align: left">工程</th> <th style="text-align: left">介绍</th> </tr> </thead> <tbody> <tr> <td style="text-align: left">1</td> <td style="text-align: left">itstack-naive-chat-ui</td> <td style="text-align: left">使用JavaFx开发的UI端,在我们的UI端中提供了;登录框体、聊天框体,同时在聊天框体中有大量的行为交互界面以及接口和事件。最终我的UI端使用Maven打包的方式向外提供Jar包,以此来达到UI界面与业务行为流程分离。</td> </tr> <tr> <td style="text-align: left">2</td> <td style="text-align: left">itstack-naive-chat-client</td> <td style="text-align: left">客户端是我们的通信核心工程,主要使用Netty4.x作为我们的socket框架来完成通信交互。并且在此工程中负责引入UI的Jar包,完成UI定义的事件(登录验证、搜索添加好友、对话通知、发送信息等等),以及需要使用我们在服务端工程定义的通信协议来完成信息的交互操作。</td> </tr> <tr> <td style="text-align: left">3</td> <td style="text-align: left">itstack-navie-chat-server</td> <td style="text-align: left">服务端同样使用Netty4.x作为socket的通信框架,同时在服务端使用Layui作为管理后台的页面,并且我们的服务端采用偏向于DDD领域驱动设计的方式与Netty集合,以此来达到我们的框架结构整洁干净易于扩展。</td> </tr> <tr> <td style="text-align: left">4</td> <td style="text-align: left">itstack.sql</td> <td style="text-align: left">系统工程数据库表结构以及初始化数据信息,共计6张核心表;用户表、群组表、用户群组关联表、好友表、对话表以及聊天记录表。用户在实际业务开发中可以自行拓展完善,目前库表结构只以核心功能为基础。</td> </tr> </tbody> </table> <ul> <li><strong>源码获取</strong>:关注公众号:bugstack虫洞栈,回复<code class="highlighter-rouge">IM</code> <em>亲,源码给我点个Star,不要白皮袄!!!</em></li> <li><strong>专栏小册</strong>:直接阅读原文即可</li> </ul> <h2 id="七总结">七、总结</h2> <p><img src="https://bugstack.cn/assets/images/2020/p-xmind.png" alt="" /></p> <ul> <li>此IM系统涉及到的技术栈内容较多,Netty4.x、SpringBoot、Mybatis、Mysql、JavaFx、layui等技术栈的使用,以及整个系统框架结构采用DDD四层架构+Socket模块的方式进行搭建,所有的UI都以前后端分离事件驱动方式进行设计,在这个过程中只要你能坚持学习下来,那么一定会收获非常多的内容。<em>足够吹牛啦!🌶</em></li> <li>任何一个新技术栈的学习过程都会包括这样一条路线;运行HelloWorld、熟练使用API、项目实践以及最后的深度源码挖掘。 那么在听到这样一个需求时候,Java程序员肯定会想到一些列的技术知识点来填充我们项目中的各个模块,例如;界面用JavaFx、Swing等,通信用Socket或者知道Netty框架、服务端控制用MVC模型加上SpringBoot等。但是怎么将这些各个技术栈合理的架设出我们的系统确是学习、实践、成长过程中最重要的部分。</li> </ul> Tue, 17 Aug 2021 00:00:00 +0800 https://bugstack.cn/itstack-demo-netty-3/2021/08/17/%E7%BB%99%E5%AD%A6%E4%B9%A0%E5%8A%A0%E7%82%B9%E5%AE%9E%E8%B7%B5-%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E5%88%86%E5%B8%83%E5%BC%8FIM(%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1)%E7%B3%BB%E7%BB%9F.html https://bugstack.cn/itstack-demo-netty-3/2021/08/17/%E7%BB%99%E5%AD%A6%E4%B9%A0%E5%8A%A0%E7%82%B9%E5%AE%9E%E8%B7%B5-%E5%BC%80%E5%8F%91%E4%B8%80%E4%B8%AA%E5%88%86%E5%B8%83%E5%BC%8FIM(%E5%8D%B3%E6%97%B6%E9%80%9A%E4%BF%A1)%E7%B3%BB%E7%BB%9F.html netty itstack-demo-netty-3 itstack-demo-netty-3