<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>ReBE</title>
  
  <subtitle>Febers的博客</subtitle>
  <link href="/atom.xml" rel="self"/>
  
  <link href="http://yoursite.com/"/>
  <updated>2019-06-09T05:03:18.000Z</updated>
  <id>http://yoursite.com/</id>
  
  <author>
    <name>Febers</name>
    
  </author>
  
  <generator uri="http://hexo.io/">Hexo</generator>
  
  <entry>
    <title>遇见大河：世界河流乐曲欣赏</title>
    <link href="http://yoursite.com/%E9%81%87%E8%A7%81%E5%A4%A7%E6%B2%B3%EF%BC%9A%E4%B8%96%E7%95%8C%E6%B2%B3%E6%B5%81%E4%B9%90%E6%9B%B2%E6%AC%A3%E8%B5%8F/"/>
    <id>http://yoursite.com/遇见大河：世界河流乐曲欣赏/</id>
    <published>2019-06-09T03:27:56.000Z</published>
    <updated>2019-06-09T05:03:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>人类社会文明起源于河流文化，在其漫长的发展中，河流扮演着至关重要的角色。从欧洲的多瑙河、伏尔加河到中国的长江黄河，从北美的密西西比河到巴西的亚马逊河，从印度的恒河到非洲大陆的尼罗河，这些伟大而历史悠久的河流构建了地球的脉络，流转出人类历史的起伏。艺术作品中的河流形象数不胜数，《清明上河图》、《伏尔加河上的纤夫》、《密西西比河上》等文学、美术作品比比皆是。恰巧最近在周海宏老师的《音乐鉴赏》课上听到了乐曲《伏尔塔瓦河》，田艺苗老师的《穿T恤听古典音乐》也曾详细介绍过该乐曲，便想用一篇文章将其记录下来，同时加入了另外两首经典的乐曲。</p><p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/a/ae/Ilia_Efimovich_Repin_%281844-1930%29_-_Volga_Boatmen_%281870-1873%29.jpg/1920px-Ilia_Efimovich_Repin_%281844-1930%29_-_Volga_Boatmen_%281870-1873%29.jpg" alt></p><h2 id="伏尔塔瓦河"><a href="#伏尔塔瓦河" class="headerlink" title="伏尔塔瓦河"></a>伏尔塔瓦河</h2><p><a href="https://music.163.com/song?id=1112818" target="_blank" rel="noopener">https://music.163.com/song?id=1112818</a> </p><p>斯美塔纳交响诗套曲《我的祖国》的第二乐章。斯美塔纳这样形容他心目中捷克的母亲河：</p><blockquote><p> 伏尔塔瓦河有两个源头——流过寒风呼啸的森林的两条溪水汇合成一道洪流，冲着鹅卵石哗哗作响，映着阳光闪烁光芒。它在森林中梭巡，聆听猎号的回音；他穿过庄稼地，饱览丰盛的收获。在它的两岸，传出乡村婚礼的欢乐声，月光下，水仙女唱着迷人的歌在浪尖上嬉戏。在近旁荒野的悬崖上，保留着昔日光荣和功勋记忆的那些城堡废墟，谛听着它的波浪喧哗。顺着圣约翰峡谷，伏尔塔瓦河奔泻而下，冲击着突岩峭壁，发出轰然巨响。尔后，河水更广阔地奔向布拉格，流经古老的维谢格拉得，现出它全部的瑰丽和庄严。伏尔塔瓦河继续滚滚向前，最后同易北河的巨流汇合并逐渐消失在远方。</p></blockquote><p>以下内容选自《穿T恤听古典音乐》</p><blockquote><p>一开始，单簧管和长笛奏出潺潺溪流，像森林中的伏尔塔瓦河源头。音乐是如何模仿的？何以如此逼真？它们的活泼速度模仿溪流的节奏，上下起伏的浅浅小旋律模仿水流的曲线。这两条小溪流逐渐汇成小河，河流依旧轻快向前，逐渐地，小河铺展成了一段伴奏，欣然等待一支音乐主题。</p><p>这首主题在弦乐队唱起的时候，人们恍然一笑，啊，原来就是这首啊。时常在广告、片头和电影里听见。这个曲调就是《伏尔塔瓦河》的主题。旋律如波浪，水波自波谷蓄积，升起，再渐渐褪去，一波一波，平缓而开阔，一幅大河的画卷在音乐中展开。旋律中还可听见一条河流的气度和描绘它的人满怀自豪感。它的平缓中激情不息，就像人们初见这条美丽的大河时心中流淌的赞叹，同时它又保留着沿岸的波希米亚风格。同一条旋律可以被多层面解读，可见这模仿哪里容易！斯美塔纳对这条大河的感情深沉无言，音乐的表白超越了任何言语。</p><p>……</p><p>紧接着，河流转弯了，这时波浪仿佛消失。一个轻快的波尔卡舞曲节奏从远处传来。这是流经哪里了，波希米亚小村庄，还是一场舞会、一场乡村婚礼？弦乐器演奏淳朴温暖的乡间民谣，节奏里似乎回荡着啤酒和布拉格香肠的朴实香味。</p><p>……</p><p>伏尔塔瓦河继续流淌。再后来，音色变了，温暖的音色转变成冷色调，背景处，弦乐的泛音晶莹，如洒在河面的银色月光。清冽的木管音色，大管、双簧管和单簧管次第铺展。顿时天色变了，曲调放缓，河流流向夜里。这月光下的河流并非类似肖邦的清丽或舒曼的幻觉，竟带着几分天真的民间神话气息。晶莹的竖琴声从水中一颗颗升起，神话的情节如银粉散开，变出几百个洁白的水仙女。听到这里，想起德沃夏克（Antonín Leopold Dvořák）有一部著名的歌剧《水仙女》，这是在捷克家喻户晓的童话。</p><p>在长笛的呼啸声里，定音鼓声在河流底部滚动，长号齐鸣，河流变得湍急，回到了最初溪流汇入大河的景象，但更澎湃激越，此时弦乐队唱起熟悉的甘美主题，宛如一股暖流。即使对一条河流的简单模仿，也拥有了抚慰人心的力量。之后乐队持续不息，铜管齐鸣，一层层沸腾，乐器、节奏、旋律，每一个漩涡都变得明快，铜管乐器带来了金色的庄严感，此时你发现河流越来越壮阔，一定是流到首都布拉格了。</p><p>网上能搜到的《伏尔塔瓦河》演奏，大多是卡拉扬指挥柏林爱乐乐团的演奏版本，堪称这一曲目的典范演绎，结构缜密，气势夺人，所有细节都一丝不苟。为这样一支名曲，树立一种无偏颇的典范演绎十分必要。而我更怀念的是1990年“布拉格之春”音乐节的那场演出，担任指挥的是捷克指挥家库贝利克（Rafael Jeroným Kubelík）。库贝利克录过6次斯美塔纳的《我的祖国》，是捷克音乐诠释的权威人物，而他的指挥倒没有卡拉扬那么“权威”，他更随和质朴一些，讲母语总是舒服的，我们听来也更舒服。那时库贝利克76岁，离开了捷克42年，第一次回国演出，也是他第二次登上“布拉格之春”这个隆重的音乐节。即使他一直旅居瑞士和美国，去世之后，仍是在捷克入葬，让灵魂与躯体融入家乡的河流与土地。音乐家的故事和乐曲交织在一起，有了悲欢离合，有了人的悲悯，音乐才有了灵魂。</p><p>《伏尔塔瓦河》不只模仿了一条河流的故事，它在模仿的过程中已经自然浇灌出了乐曲的结构。音乐的结构与文学不太一样。文学有故事，有具体情节，音乐可以模仿人物事件，但至多是暗示，并不确定，听者的想象以主观成分居多。音乐有它自己的逻辑，这逻辑遵循人的听觉记忆的自然原理。音乐主题若是一闪而过，人们会记不住，也不过瘾，于是主题要重复，但重复太多人们又会腻烦，所以作曲家会变化重复，比如转调，变化和声、音色、节奏。而在一定的重复之后，需要一个对比段落，让听者振奋情绪，并塑造音乐中的矛盾冲突，于是相应而生音乐性格对比的段落，在经历了一系列的变化与发展之后，再来一个最终的主题再现，以各种动力的、激情的方式重申主题，类似文学作品最后的大结局。在古典主义时代之前，最后的再现段落往往照搬呈示部，不予变化，后来为了更饱满更动人的情感表达，再现部往往更有动力，规模庞大，飞流直下浩浩汤汤，直到乐曲结束。</p><p>《伏尔塔瓦河》的第一段，是河流生长与成型的呈示段；接着河流经过小村庄，就有了节奏与情节对比的波尔卡舞曲段落；第三段仍是对比，变的是音色，并将音乐的内在节拍放慢，展示音乐情绪与音色的对比；第四段是再现，乐队全奏，辉煌收尾。如此结构，既是一条河流的描述，也遵循呈示、对比（展开）、再现的三明治式音乐结构原则。</p></blockquote><h2 id="莫斯科河上的黎明"><a href="#莫斯科河上的黎明" class="headerlink" title="莫斯科河上的黎明"></a>莫斯科河上的黎明</h2><p><a href="https://music.163.com/song?id=1351564275" target="_blank" rel="noopener">https://music.163.com/song?id=1351564275</a></p><p>该曲为穆索尔斯基的歌剧《霍万兴那》的前奏曲，是一首富有诗意的前奏曲，可脱离歌剧的内容去欣赏。以下赏析内容可忽略：</p><blockquote><p>乐曲描绘当年莫斯科的清晨景色，矮小的木头房子，散置耸起的贵族府邸的尖屋顶和教堂的镀金圆顶，还有在朦胧的晨曦中隐约可辨认出轮廓的克里姆林宫和教堂，天空刚现出朝霞，朝阳的光芒让高高突起的尖的和圆的屋顶镶上了一道金边，耀眼的太阳终于升起……</p><p>乐曲开始，中提琴和长笛奏出来自基本主题的引子，在寂静中像袅袅上升的朝霞，逐渐转入高音区，由第二小提琴和单簧管覆奏，结合着此起彼落仿似是公鸡的啼叫声，营造出晨曦景象。随后是乐曲的基本主题和四次变奏。基本主题源出于抒缓的俄罗斯抒情歌曲旋律，宽广、明亮、和谐而真挚。</p><p>这个基本主题分别在不同的乐器出现，调性不断更换，每次变奏音响都更饱满、更明亮。夜巡归来的士兵的步履声，教堂清晨的钟声等，将音响力度逐渐加强，将音乐的色调增浓，生动地描画出从黎明前的阴晦，转变为清朗的白昼景象。乐曲结尾再次减弱，但色彩更明朗清澈;最后长笛和单簧管交替，模仿牧笛的呼应，最后在慢慢消逝的柔美圆号声中结束。</p><p>(周凡夫)</p></blockquote><h2 id="蓝色多瑙河"><a href="#蓝色多瑙河" class="headerlink" title="蓝色多瑙河"></a>蓝色多瑙河</h2><p><a href="https://music.163.com/song?id=5276811" target="_blank" rel="noopener">https://music.163.com/song?id=5276811</a></p><p>小约翰·斯特劳斯的经典圆舞曲，按照典型的维也纳圆舞曲的结构写成，由序奏、五个小圆舞曲和尾声组成：</p><blockquote><p>序奏开始时，小提琴在 A 大调上用碎弓轻轻奏出徐缓的震音，好似黎明的曙光拨开河面上的薄雾，唤醒了沉睡大地，多瑙河的水波在轻柔地翻动。接下来是五首连着一起演奏的小圆舞曲，每首小圆舞曲都包含两个相互对比的主题旋律。整首乐曲优美典雅、欢快迷人。</p></blockquote><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;人类社会文明起源于河流文化，在其漫长的发展中，河流扮演着至关重要的角色。从欧洲的多瑙河、伏尔加河到中国的长江黄河，从北美的密西西比河到巴西的亚马逊河，从印度的恒河到非洲大陆的尼罗河，这些伟大而历史悠久的河流构建了地球的脉络，流转出人类历史的起伏。艺术作品中的河流形象数不胜数
      
    
    </summary>
    
      <category term="艺术" scheme="http://yoursite.com/categories/%E8%89%BA%E6%9C%AF/"/>
    
    
      <category term="音乐" scheme="http://yoursite.com/tags/%E9%9F%B3%E4%B9%90/"/>
    
      <category term="河流" scheme="http://yoursite.com/tags/%E6%B2%B3%E6%B5%81/"/>
    
  </entry>
  
  <entry>
    <title>缺陷与完美：绘本分享《失落的一角》</title>
    <link href="http://yoursite.com/%E7%BC%BA%E9%99%B7%E4%B8%8E%E5%AE%8C%E7%BE%8E%EF%BC%9A%E7%BB%98%E6%9C%AC%E5%88%86%E4%BA%AB%E3%80%8A%E5%A4%B1%E8%90%BD%E7%9A%84%E4%B8%80%E8%A7%92%E3%80%8B/"/>
    <id>http://yoursite.com/缺陷与完美：绘本分享《失落的一角》/</id>
    <published>2019-06-06T15:58:48.000Z</published>
    <updated>2019-06-07T04:16:00.000Z</updated>
    
    <content type="html"><![CDATA[<p>出门打球的时候快十点了，十一点回来，大汗淋漓。等待洗澡间隙，逛 V2  看到一个半年前的帖子<a href="https://www.v2ex.com/t/516411" target="_blank" rel="noopener">如何认识更多有趣的人？</a>，颇有感触。看到一个评论，挺有意思的：</p><blockquote><p>你喜欢星巴克吗？你会去寻找好喝的、十几二十块钱一杯的、可能排队半小时的 XX 茶吗？<br>你热衷于 K-POP 文化吗？你会在微博上为自己的粉丝刷赞刷转发，给自己的偶像撑场子吗？<br>你喜欢电音吗？你会去音乐节感受人的海洋吗？你网易云音乐有十级吗？你对耳机有所追求吗？<br>你喜欢看电影吗？你会一个人去看电影吗？你更喜欢 DC 还是漫威？你觉得伏地魔和格林德沃谁更强？<br>……</p><p>或者，以上这些你喜欢的不多，你只是个普通的、没什么空余时间的上班族。<br>你或许搭乘交通工具，在城市中穿梭往来，每天上班、下班，在人流中穿梭，每天只是看自己的手机、吃自己的饭、听自己的歌。</p><p>……</p></blockquote><p>想到还在清水河的时候，不下十次一个人跑去电影院，看感兴趣的片子，不由自主地笑了。</p><p>评论里有人推荐谢尔的《失落的一角》，原来是简单的绘本。作者用寥寥几语和简洁线条，叙述了一个耐人寻味的寓言。</p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_01.png" alt></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_02.png" alt="失落的一角_02"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_03.png" alt="失落的一角_03"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_04.png" alt="失落的一角_04"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_05.png" alt="失落的一角_05"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_06.png" alt="失落的一角_06"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_07.png" alt="失落的一角_07"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_08.png" alt="失落的一角_08"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_09.png" alt="失落的一角_09"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_10.png" alt="失落的一角_10"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_11.png" alt="失落的一角_11"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_12.png" alt="失落的一角_12"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_13.png" alt="失落的一角_13"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_14.png" alt="失落的一角_14"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_15.png" alt="失落的一角_15"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_16.png" alt="失落的一角_16"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_17.png" alt="失落的一角_17"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_18.png" alt="失落的一角_18"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_19.png" alt="失落的一角_19"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_20.png" alt="失落的一角_20"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_21.png" alt="失落的一角_21"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_22.png" alt="失落的一角_22"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_23.png" alt="失落的一角_23"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_24.png" alt="失落的一角_24"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_25.png" alt="失落的一角_25"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_26.png" alt="失落的一角_26"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_27.png" alt="失落的一角_27"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_28.png" alt="失落的一角_28"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_29.png" alt="失落的一角_29"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_30.png" alt="失落的一角_30"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_31.png" alt="失落的一角_31"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_32.png" alt="失落的一角_32"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_33.png" alt="失落的一角_33"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_34.png" alt="失落的一角_34"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_35.png" alt="失落的一角_35"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_36.png" alt="失落的一角_36"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_37.png" alt="失落的一角_37"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_38.png" alt="失落的一角_38"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_39.png" alt="失落的一角_39"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_40.png" alt="失落的一角_40"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_41.png" alt="失落的一角_41"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_42.png" alt="失落的一角_42"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_43.png" alt="失落的一角_43"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_44.png" alt="失落的一角_44"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_45.png" alt="失落的一角_45"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_46.png" alt="失落的一角_46"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_47.png" alt="失落的一角_47"></p><p><img src="/缺陷与完美：绘本分享《失落的一角》/失落的一角_48.png" alt="失落的一角_48"></p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;出门打球的时候快十点了，十一点回来，大汗淋漓。等待洗澡间隙，逛 V2  看到一个半年前的帖子&lt;a href=&quot;https://www.v2ex.com/t/516411&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;如何认识更多有趣的人？&lt;/a&gt;，颇有感触
      
    
    </summary>
    
      <category term="艺术" scheme="http://yoursite.com/categories/%E8%89%BA%E6%9C%AF/"/>
    
    
      <category term="艺术" scheme="http://yoursite.com/tags/%E8%89%BA%E6%9C%AF/"/>
    
      <category term="绘本" scheme="http://yoursite.com/tags/%E7%BB%98%E6%9C%AC/"/>
    
      <category term="人生" scheme="http://yoursite.com/tags/%E4%BA%BA%E7%94%9F/"/>
    
  </entry>
  
  <entry>
    <title>切斯瓦夫·米沃什诗歌节选</title>
    <link href="http://yoursite.com/%E5%88%87%E6%96%AF%E7%93%A6%E5%A4%AB%C2%B7%E7%B1%B3%E6%B2%83%E4%BB%80%E8%AF%97%E6%AD%8C%E8%8A%82%E9%80%89/"/>
    <id>http://yoursite.com/切斯瓦夫·米沃什诗歌节选/</id>
    <published>2019-06-04T14:25:22.000Z</published>
    <updated>2019-06-06T05:16:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>切斯瓦夫·米沃什是著名的美籍波兰诗人、散文家、文学史家。作者生于 1911 年，逝世于 2004 年，1980年获得诺贝尔文学奖。<a id="more"></a></p><h3 id="礼物"><a href="#礼物" class="headerlink" title="礼物"></a>礼物</h3><blockquote><p>如此幸福的一天<br>雾早就散了<br>我在花园里干活<br>蜂鸟停在忍冬花上</p><p>这世上没有一样东西我想占有<br>我知道没有一个人值得我羡慕</p><p>任何我曾遭遇的不幸<br>我都已忘记</p><p>想到故我今我同为一人并不使我难为情<br>在我身上没有痛苦</p><p>直起身来<br>我望见蓝色的大海和帆影</p></blockquote><h3 id="窗"><a href="#窗" class="headerlink" title="窗"></a>窗</h3><blockquote><p>黎明时我向窗外了望<br>见棵年轻的苹果树沐着曙光</p><p>又一个黎明我望着窗外<br>苹果树已经是果实累累</p><p>可能过去了许多岁月<br>睡梦里出现过什么，我再也记不起</p></blockquote><h3 id="可怜的诗人"><a href="#可怜的诗人" class="headerlink" title="可怜的诗人"></a>可怜的诗人</h3><blockquote><p>最初的动作是歌唱<br>一种自由的声音，充塞山谷<br>最初的动作是喜悦<br>但它已被攫去</p><p>既然岁月已经改变了我的血<br>而成千的行星系统在我的肉体中生生死死<br>我坐着，一个灵巧而愤怒的诗人<br>眼睛斜视，满怀恶意<br>手中，掂量着笔<br>我密谋着复仇</p><p>我掌握着笔而它长出枝叶，满覆着花朵<br>而那树的气味是莽撞无礼的，因为在那现实的地球上<br>并不长有这种树，而那树的气味<br>对受苦的人类，像是一种侮辱</p><p>有些人避难于绝望，它甘美<br>如强烈的菸草，如在虚无时喝醉的一杯伏特加<br>其他的抱着蠢人的希望，玫红如淫艳的梦</p><p>另有一些人在爱国的盲目崇拜中找到安宁<br>它可以维持很久<br>虽然并不比十九世纪维持得更久</p><p>然而给我的却是一种冷嘲热讽的希望<br>因为自从睁开眼睛，我只看见火光、大屠杀<br>只见背信、侮辱，以及吹牛者可笑的羞耻<br>给我的是对别人与对自己复仇的希望<br>因为我是个了解它<br>而不为自己从中取利的人</p></blockquote><h3 id="逃亡"><a href="#逃亡" class="headerlink" title="逃亡"></a>逃亡</h3><blockquote><p>当我们离开那燃烧中的城市时<br>在第一条野径上，掉头回顾<br>我说∶”让野草覆盖我们的脚印吧<br>让无情的先知在火中沉默<br>且让死者告诉生者所发生的事<br>我们注定要生出一个新的、勇猛的种族<br>免于在那儿昏睡的罪恶与快乐<br>我们走吧 “于是一把火剑为我们劈开大地</p></blockquote><h3 id="哀歌"><a href="#哀歌" class="headerlink" title="哀歌"></a>哀歌</h3><blockquote><p>告诉我，对你是否太远<br>你原可奔过波罗地海的微浪<br>经过丹麦田野，经过山毛鹇树林<br>原可转向海洋，而那儿，不久<br>拉布拉多，在这时节是白色的<br>假如你，梦想一个孤岛的你<br>害怕城市以及公路沿途闪亮的灯光<br>你有一条小径直穿原野<br>俯视一片墨色溶溶的水面，野鹿与美洲驯鹿的足迹<br>远至锯齿山脉与放弃的金矿区<br>萨克拉门托河，原可引导你<br>在长满多刺橡树的山丘之间<br>然后只有尤隹利树林，而你找到了我</p><p>真的，当石南盛开<br>而海湾晴朗，在春日早晨<br>我无可奈何地想到，在那些湖<br>与立陶宛天空下拉上的网之间，那楝房子<br>你从前放衣服的浴室小房间<br>已永远变成一个抽象的水晶品<br>如蜜的黑暗在那儿，靠近游廊<br>以及好玩的小猫头鹰，以及皮革的气味</p><p>那时一个人怎能活下去，我真的不知道<br>神采与服装若隐若现，朦朦然<br>非自足的，趋向终局<br>我们渴望事物本身的原貌，这要不要紧<br>对火般岁月的了解烧焦了站在锻铁场那些马<br>市场里那些小圆柱<br>那些木梯，以及弗理吉尔托普妈妈的假发</p><p>我们学了那么多，这点你很知道∶<br>如何，逐渐地，不可能被剥夺<br>被剥夺。人民，乡村<br>而心并没有死，当人们以为它应该已死<br>我们微笑，桌上有茶和面包。<br>而且只悔恨我们没爱<br>在沙克森豪森的可怜的骨灰<br>以绝对的爱，超乎人的力量</p><p>你已习惯于新的、潮湿的冬天<br>习惯于别墅，那儿，德国主人的血<br>从墙上被洗掉，而他永远不再回来<br>我也接受可能以外的一切，城市和乡村<br>一个人不能两次踏进同一个湖<br>在赤杨的朽叶上<br>折断一道狭长的阳光</p><p>罪，你我的？不是大罪<br>秘密，你我的？不是大秘密<br>不是，当他们用手帕绑住下颚，将一个小十字架放在手指间<br>而某个地方狗吠，第一颗星突然闪亮</p><p>不，不是因为太远<br>那天或晚上你没有来造访我<br>年复一年，它在我们心中滋长，直到它完全掌握<br>我了解它，正如你一样∶泠漠</p></blockquote><h3 id="那么少"><a href="#那么少" class="headerlink" title="那么少"></a>那么少</h3><blockquote><p>我说得那么少<br>日子短促</p><p>短暂的白昼<br>短暂的夜晚<br>短暂的岁月</p><p>我说得那么少<br>我不能继续说下去<br>我的心滋生着疲倦<br>由于喜悦<br>失望<br>热情<br>希望</p><p>海中巨兽的颚骨<br>紧咬着我</p><p>赤裸着，我躺在荒岛的<br>岸上</p><p>世界白色的鲸鱼<br>把我拖向它的深渊</p><p>现在我不知道<br>在一切中什么是真实</p></blockquote><h3 id="蟒蛇"><a href="#蟒蛇" class="headerlink" title="蟒蛇"></a>蟒蛇</h3><blockquote><p>我想说出真相<br>但感觉徒然</p><p>我试图坦白<br>却没有什么能够坦白</p><p>我不相信精神疗法<br>我知道我会说出不少谎言</p><p>如此，我带给自己一条缠绕着的愧疚——<br>它于我不是一个抽象的概念</p><p>我站在雅斯朱尼的拉乌杜尼卡湿地<br>一条蟒蛇拖着尾巴正消失在<br>矮松林下的苔藓</p><p>我扣动扳机，铅弹射出了霰弹枪<br>直到今天我也不知道是否有粒子弹<br>击中那可怕的白腹<br>或它背上的条形纹</p><p>较之心灵的种种冒险，无论如何<br>这叙述要容易得多</p></blockquote><h3 id="使命"><a href="#使命" class="headerlink" title="使命"></a>使命</h3><blockquote><p>在畏惧和颤栗中，我想我会完成我的使命<br>只当我促使自己提出公开的自白书<br>揭示我自己和我这时代的羞耻<br>我们被允许以侏儒和恶魔的口舌尖叫<br>而真纯和宽宏的话却被禁止<br>在如此严峻的惩罚下，谁敢说出一个字<br>谁就自认为是个失踪的人</p></blockquote><h3 id="尾声"><a href="#尾声" class="headerlink" title="尾声"></a>尾声</h3><blockquote><p>因此是你的命运挥动你的魔杖<br>唤醒暴风雨，冲过暴风雨的中心<br>暴露纪念碑像灌木丛中的巢<br>虽然你曾想要的只是摘一些玫瑰</p></blockquote><h3 id="大地重光"><a href="#大地重光" class="headerlink" title="大地重光"></a>大地重光</h3><blockquote><p>我来了，何必这种莫名的恐惧<br>不久黑夜将离去，白天将升起<br>你听：牧羊人的号角已经<br>吹响。星光逐渐消失于红曦</p><p>“大道”很直：我们在边上<br>钟声敲响在下面的村庄<br>而篱笆上公鸡在欢迎<br>曙光；大地肥沃而快乐，冒着热气</p><p>这儿仍是黑暗，像泛滥的河水<br>浓雾笼罩黑簇簇的越橘<br>然而踩着高跷的黎明已进入水中<br>而带着铃声日球在滚动</p></blockquote><hr><p>感谢译诗库<a href="http://www.shigeku.org/xlib/lingshidao/yishi/miiosz.htm" target="_blank" rel="noopener">米沃什诗选</a></p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;切斯瓦夫·米沃什是著名的美籍波兰诗人、散文家、文学史家。作者生于 1911 年，逝世于 2004 年，1980年获得诺贝尔文学奖。&lt;/p&gt;
    
    </summary>
    
      <category term="文学" scheme="http://yoursite.com/categories/%E6%96%87%E5%AD%A6/"/>
    
    
      <category term="切斯瓦夫·米沃什" scheme="http://yoursite.com/tags/%E5%88%87%E6%96%AF%E7%93%A6%E5%A4%AB%C2%B7%E7%B1%B3%E6%B2%83%E4%BB%80/"/>
    
      <category term="文学" scheme="http://yoursite.com/tags/%E6%96%87%E5%AD%A6/"/>
    
  </entry>
  
  <entry>
    <title>《Effective Java》读书笔记（一）：创建和销毁对象</title>
    <link href="http://yoursite.com/%E3%80%8AEffective-Java%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E5%88%9B%E5%BB%BA%E5%92%8C%E9%94%80%E6%AF%81%E5%AF%B9%E8%B1%A1/"/>
    <id>http://yoursite.com/《Effective-Java》读书笔记（一）：创建和销毁对象/</id>
    <published>2019-06-04T14:21:51.000Z</published>
    <updated>2019-06-05T07:21:08.000Z</updated>
    
    <content type="html"><![CDATA[<p>Spring 系列还在进行，又开了新坑（苦笑）。这一系列基于《Effective Java》——相比《Spring in Action》，标题就友好很多，大部分是 Java 的一些编程思想，探讨如何写出简洁、高效和健壮的代码，写起来会很轻松（<del>毕竟就是抄嘛</del>）。这本书买了有一年多，反复翻阅了几次。记忆最深的是，有一次从成都到武汉，因为赶高铁不及，只能转坐绿皮火车。在车上的二十多个小时，大部分时间花在这本书上。算是一本质量很高的指导手册。<a id="more"></a></p><h2 id="用静态工厂方法代替构造器"><a href="#用静态工厂方法代替构造器" class="headerlink" title="用静态工厂方法代替构造器"></a>用静态工厂方法代替构造器</h2><p>比如下面的代码，来自 Boolean 的简单示例</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Boolean <span class="title">valueOf</span><span class="params">(<span class="keyword">boolean</span> b)</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> b ? Boolean.TRUE : Boolean.FALSE</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>通过静态工厂方法（区别于设计模式中的工厂方法），代替传统的公有构造器，向客户端提供实例，这么做的优势在于</p><ul><li><p>静态工厂方法具有名称：这是显而易见的。另一方面，拥有多个参数的类很可能提供多个构造器，如何区分这些构造器就是问题。</p></li><li><p>静态工厂方法可以提供单例：从这个角度看，相当于实现了简单的单例模式。</p></li><li><p>静态工厂方法可以返回任何子类型的对象：典型的应用为<code>Java.util.Collections</code>类，其中定义了很多静态内部类比如<code>EmptyList</code>，并通过静态方法<code>emptyList</code>返回实现</p></li><li><p>静态工厂方法可以提供更简洁的实例化代码：实际上在较新的 JDK 版本已经去掉了多余的类型参数，但是静态工程方法确实可以做到更简洁</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line">List< String> strings = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line">List< String> emptyList = Collections.emptyList();</span><br></pre></td></tr></tbody></table></figure></li></ul><p>静态工厂方法的缺点在于，第一如果类不包含公有构造器，则外部无法继承它——勉强算一个缺点吧——第二它与其他静态方法没有任何区别，只是返回的是自身的一个实例。这就造成如何类没有提供公有构造方法，那么外部调用者将苦恼于它的实例化。下面是一些静态工厂方法的惯用名称：</p><ul><li><code>valueOf</code>：实际上属于类型转换方法</li><li><code>of</code>：上面名称的简洁形式</li><li><code>getInstance</code>：返回的实例通过方法参数描述。在单例模式中保证返回唯一的实例</li><li><code>newInstance</code>：跟上面的相似，但保证返回的实例与所有其他实例不同</li><li><code>getType</code>：不了解，<code>Character</code>提供了该工厂静态方法返回<code>CharacterData</code>的不同实现</li><li><code>newType</code>：不了解</li></ul><h2 id="多个构造器时考虑使用构建器"><a href="#多个构造器时考虑使用构建器" class="headerlink" title="多个构造器时考虑使用构建器"></a>多个构造器时考虑使用构建器</h2><p>实际上就是使用简单的建造者模式实例化对象，还是通过代码说明</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Human</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> String name;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> height;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> weight;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Builder</span> </span>{</span><br><span class="line">        <span class="comment">// 必要参数</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> name;</span><br><span class="line"></span><br><span class="line">        <span class="comment">// 可选参数</span></span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">int</span> height = <span class="number">170</span>;</span><br><span class="line">        <span class="keyword">private</span> <span class="keyword">int</span> weight = <span class="number">60</span>;</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> <span class="title">Builder</span><span class="params">(String name)</span> </span>{</span><br><span class="line">            <span class="keyword">this</span>.name = name</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Builder <span class="title">height</span><span class="params">(<span class="keyword">int</span> height)</span> </span>{</span><br><span class="line">            <span class="keyword">this</span>.height = height;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Builder <span class="title">weight</span><span class="params">(<span class="keyword">int</span> weight)</span> </span>{</span><br><span class="line">            <span class="keyword">this</span>.weight = weight;</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        <span class="function"><span class="keyword">public</span> Human <span class="title">build</span><span class="params">()</span> </span>{</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">new</span> Human(<span class="keyword">this</span>);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">Human</span><span class="params">(Builder builder)</span> </span>{</span><br><span class="line">        name = builder.name;</span><br><span class="line">        height = builder.height;</span><br><span class="line">        weight = builder.weight;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line">        Human human = <span class="keyword">new</span> Human.Builder(<span class="string">"Jack"</span>)</span><br><span class="line">                    .height(<span class="number">175</span>)</span><br><span class="line">                    .weight(<span class="number">60</span>)</span><br><span class="line">                    .build();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>这种方式广泛运用于 Java 开发中，优点显而易见，唯一的缺点可能为了创建对象需要先创建其建造器（不值一提）</p><h2 id="使用私有构造器、枚举强化单例"><a href="#使用私有构造器、枚举强化单例" class="headerlink" title="使用私有构造器、枚举强化单例"></a>使用私有构造器、枚举强化单例</h2><p>其实就是单例模式，简单贴一下代码吧，忘记了可以回去看设计模式的笔记：<a href="https://febers.github.io/Java%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%EF%BC%9A%E5%88%9B%E5%BB%BA%E5%9E%8B%E6%A8%A1%E5%BC%8F/" target="_blank" rel="noopener">Java设计模式：创建型模式</a></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Singleton</span> </span>{  </span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">volatile</span> <span class="keyword">static</span> Singleton singleton;  </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">Singleton</span> <span class="params">()</span></span>{}  </span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Singleton <span class="title">getSingleton</span><span class="params">()</span> </span>{  </span><br><span class="line">        <span class="keyword">if</span> (singleton == <span class="keyword">null</span>) {  </span><br><span class="line">            <span class="keyword">synchronized</span> (Singleton.class) {  </span><br><span class="line">                <span class="keyword">if</span> (singleton == <span class="keyword">null</span>) {  </span><br><span class="line">                    singleton = <span class="keyword">new</span> Singleton();  </span><br><span class="line">                }  </span><br><span class="line">            }  </span><br><span class="line">        }  </span><br><span class="line">        <span class="keyword">return</span> singleton;  </span><br><span class="line">    }  </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>下面是枚举类型实现单例</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">enum</span>  Singleton {</span><br><span class="line">    INSTANCE</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="通过私有构造器强化不可实例化的能力"><a href="#通过私有构造器强化不可实例化的能力" class="headerlink" title="通过私有构造器强化不可实例化的能力"></a>通过私有构造器强化不可实例化的能力</h2><p>用得最多的地方就是各种<code>Util</code>类，一般只有一个静态方法，实现一个简单的功能。最典型的例子就是<code>java.lang.Math</code></p><p><del>划水而过</del></p><h2 id="避免创建不必要的对象"><a href="#避免创建不必要的对象" class="headerlink" title="避免创建不必要的对象"></a>避免创建不必要的对象</h2><p>最常见的例子就是 Java 的基本数据类型，直接使用<code>int</code>、<code>char</code>而不是它们的包装类型<code>Integer</code>、<code>Character</code>，因为基本数据类型直接保存在内存模型中的栈中，而且具有复用性，比对象类型更加节省内存</p><p>Java 的经典面试题中经常牵扯到<code>String</code>的实例化</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line">String s0 = <span class="string">"9527"</span>;</span><br><span class="line">String s1 = <span class="string">"9527"</span>;</span><br><span class="line">System.out.println(s0 == s1);</span><br><span class="line"></span><br><span class="line">String s2 = <span class="keyword">new</span> String(<span class="string">"9527"</span>);</span><br><span class="line">System.out.println(s0 == s2);</span><br></pre></td></tr></tbody></table></figure><p>（第一个比较是多余的，因为对于基本类型来说，<code>==</code>会直接比较他们的值）。<strong>一定范围内</strong>的基本数据类型会保存在栈内的常量池中。第一行代码，虚拟机首先创建一个引用<code>s0</code>，然后到常量池中寻找是否存在字面量为<code>9527</code>的值，如果有则直接返回栈地址，没有则新建一个常量<code>9527</code>并将<code>s0</code>指向它，然后返回。到了<code>s1</code>被创建的时候，根据上面的步骤，<code>s1</code>将直接指向<code>s0</code>所指向的栈地址</p><p>第二个比较结果显然是<code>false</code>。如果使用构造器构造一个<code>String</code>类型的变量，则虚拟机会先在栈中创建引用<code>s2</code>，在堆中开辟内存新建<code>String</code>对象，保存传入的参数<code>9527</code>，然后将堆地址指向栈内的<code>s2</code></p><p>对于<code>String</code>来说，直接使用构造器更糟糕的一点还在于，其被声明为<code>final</code>，每<code>new</code>一次就创建一个对象。可以想象如果在循环中使用了构造器来创建字符串，将会造成多么大的内存浪费</p><h2 id="消除过期的对象引用"><a href="#消除过期的对象引用" class="headerlink" title="消除过期的对象引用"></a>消除过期的对象引用</h2><p>通过一个栈的实现说明</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Stack</span> </span>{</span><br><span class="line">    <span class="keyword">private</span> Object[] elements;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">int</span> size;</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">int</span> DEFAULT_INITIAL_CAPACITY = <span class="number">16</span>;</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">Stack</span><span class="params">()</span> </span>{</span><br><span class="line">        elements = <span class="keyword">new</span> Object[DEFAULT_INITIAL_CAPACITY];</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">push</span><span class="params">(Object o)</span> </span>{</span><br><span class="line">        ensureCapacity();</span><br><span class="line">        elements[size++] = o;</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">public</span> Object <span class="title">pop</span><span class="params">()</span></span>{</span><br><span class="line">        <span class="keyword">if</span> (size == <span class="number">0</span>) </span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> EmptyStackException();</span><br><span class="line">        <span class="keyword">return</span> elements[--size];</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">ensureCapacity</span><span class="params">()</span> </span>{</span><br><span class="line">        <span class="keyword">if</span> (elements.length == size) {</span><br><span class="line">            elements = Arrays.copyOf(elements, <span class="number">2</span> * size + <span class="number">1</span>);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该实现的问题出现在，<code>pop</code>方法中弹出的对象将不会被系统回收，因为栈内部仍然维护着它们的过期引用，解决办法如下</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">pop</span><span class="params">()</span></span>{</span><br><span class="line">    <span class="keyword">if</span> (size == <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> EmptyStackException();</span><br><span class="line">    Object result = elements[--size];</span><br><span class="line">    elements[size] = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="避免使用终结方法"><a href="#避免使用终结方法" class="headerlink" title="避免使用终结方法"></a>避免使用终结方法</h2><p>终结方法其实就是<code>Object</code>内的<code>finalize</code>方法，其声明为</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">finalize</span><span class="params">()</span> <span class="keyword">throws</span> Throwable </span>{ }</span><br></pre></td></tr></tbody></table></figure><p>该方法是当 JVM 发现不可达对象时，将其回收之前调用的方法。问题在于，从发现该回收对象到真正回收这之间的时间是不确定的。JVM 不但不保证终结方法会被及时执行，甚至不保证其会被执行，如果重写终结方法，并期望在其中添加资源回收业务，将造成巨大隐患——资源可能永远都不会被回收。</p><p>使用终结方法的另一个弊端在于，如果子类重写了父类的<code>finalize</code>，但是却没有在其中调用父类的<code>finalize</code>，那么父类（非  Object）永远不会被回收。</p><hr><p>好了，这就是第一章的<del>划水</del>内容。可以看到大都是一些有用的编程经验和技巧，用来时刻提醒自己不要犯低级的错误。可能这本书提及到的内容有些开发者一辈子都碰不到，但是要想成为一个卓越的工程师，注重细节和原理永远是不可获取的。这也算是对自己的勉励吧。</p><hr><p>不知道是不是前面写 Dart、Gradle、Spring 的东西太多，突然这么轻松地写博客，竟然感到“周身血气运转通畅、心情愉悦”，简直好惨一博主。那下一篇也继续划水吧~</p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Spring 系列还在进行，又开了新坑（苦笑）。这一系列基于《Effective Java》——相比《Spring in Action》，标题就友好很多，大部分是 Java 的一些编程思想，探讨如何写出简洁、高效和健壮的代码，写起来会很轻松（&lt;del&gt;毕竟就是抄嘛&lt;/del&gt;）。这本书买了有一年多，反复翻阅了几次。记忆最深的是，有一次从成都到武汉，因为赶高铁不及，只能转坐绿皮火车。在车上的二十多个小时，大部分时间花在这本书上。算是一本质量很高的指导手册。&lt;/p&gt;
    
    </summary>
    
      <category term="Java" scheme="http://yoursite.com/categories/Java/"/>
    
    
      <category term="Java" scheme="http://yoursite.com/tags/Java/"/>
    
      <category term="Effective" scheme="http://yoursite.com/tags/Effective/"/>
    
  </entry>
  
  <entry>
    <title>Spring 学习笔记（四）：构建 Web 应用程序</title>
    <link href="http://yoursite.com/Spring-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E5%9B%9B%EF%BC%89%EF%BC%9A%E6%9E%84%E5%BB%BA-Web-%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F/"/>
    <id>http://yoursite.com/Spring-学习笔记（四）：构建-Web-应用程序/</id>
    <published>2019-06-03T10:56:48.000Z</published>
    <updated>2019-06-04T10:06:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>Spring MVC 基于模型-视图-控制器（ Model-View-Controller，MVC ）模式实现，用以构建灵活和松耦合的 Web 应用程序。本文将通过一个社交网站<code>Spittr</code>的实例，介绍 Spring MVC 框架，并在其基础上构建处理各种 Web 请求、参数和表单输入的控制器。</p><h2 id="Spring-MVC"><a href="#Spring-MVC" class="headerlink" title="Spring MVC"></a>Spring MVC</h2><h3 id="跟踪请求"><a href="#跟踪请求" class="headerlink" title="跟踪请求"></a>跟踪请求</h3><p><img src="/Spring-学习笔记（四）：构建-Web-应用程序/spring-mvc-request.jpeg" alt></p><p>当用户在 Web 浏览器点击链接或提交表格的时候，发出的请求将通过一系列的路径，直到获取响应返回浏览器，这一过程在 Spring MVC 中的过程可以由上图展现</p><ul><li>请求首先到达 DispatcherServlet，相当于一个前端控制器，其作用是查询处理器映射（Handler Mapping）后将请求发送给不同的 Spring MVC 控制器（Controller）</li><li>到达具体的控制器之后请求会卸下其负载信息，并等待控制器的处理结果</li><li>控制器完成逻辑处理后返回原始模型（Model）和视图名（View）</li><li>DispatcherServlet 通过视图解析器（View Resolver）解析视图名得到匹配结果</li><li>请求最后到达视图实现（比如 JSP），交付模型数据</li><li>视图实现渲染输出模型并通过响应对象传递给客户端</li></ul><h3 id="搭建-Spring-MVC"><a href="#搭建-Spring-MVC" class="headerlink" title="搭建 Spring MVC"></a>搭建 Spring MVC</h3><p><img src="/Spring-学习笔记（四）：构建-Web-应用程序/项目结构.png" alt></p><p>项目的结构图如上</p><p>Spring MVC 的核心是 DispatcherServlet，此处通过 Java 代码实现其配置，web.xml 文件的配置方式省略</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SpittrWebApplicationInitializer</span>: <span class="type">AbstractAnnotationConfigDispatcherServletInitializer</span></span>() {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">getServletMappings</span><span class="params">()</span></span>: Array<String> = arrayOf(<span class="string">"/"</span>)</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">getRootConfigClasses</span><span class="params">()</span></span>: Array<Class<*>> = arrayOf(RootConfig::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">getServletConfigClasses</span><span class="params">()</span></span>: Array<Class<*>> = arrayOf(WebConfig::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>原理（特别绕）：拓展 AbstractAnnotationConfigDispatcherServletInitializer 的类都会自动配置 DispatcherServlet 和 Spring 应用上下文（由 ContextLoaderListener 实现），因为容器会在类路径中查找实现 ServletContainerInitializer 接口的实现并为其配置，而Spring 中的实现为 SpringServletContainerInitializer，这个实现又会查找 WebApplicationInitializer——关于这个接口的继承关系为</p><blockquote><p>public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer</p><p>public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer</p><p>public abstract class AbstractAnnotationConfigDispatcherServletInitializer extends AbstractDispatcherServletInitializer</p></blockquote></blockquote><p>我们重写了三个方法</p><ul><li><code>getServletMappings</code>：将“/”路径映射到 DispatcherServlet 上，这也让其成为默认 Servlet</li><li><code>getRootConfigClasses</code>：该方法返回的带有<code>@Configuration</code>注解的类将会用来配置应用上下文中的 bean</li><li><code>getServletConfigClasses</code>：要求 DispatcherServlet 加载应用上下文时，使用定义在 WebConfig 中的 bean</li></ul><p>RootConfig 和 WebConfig 的实现如下</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(basePackages = [<span class="meta-string">"spittr"</span>], excludeFilters = [ComponentScan.Filter(type = FilterType.ANNOTATION, value = [EnableWebMvc::class])</span>])</span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">RootConfig</span></span></span><br><span class="line"></span><br><span class="line"><span class="meta">@EnableWebMvc</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan(value = [<span class="meta-string">"spittr.web"</span>])</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">WebConfig</span>: <span class="type">WebMvcConfigurerAdapter</span></span>() {</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">viewResolver</span><span class="params">()</span></span>: ViewResolver = InternalResourceViewResolver().apply {</span><br><span class="line">        setPrefix(<span class="string">"/WEB-INF/views/"</span>)</span><br><span class="line">        setSuffix(<span class="string">".jsp"</span>)</span><br><span class="line">        setExposeContextBeansAsAttributes(<span class="literal">true</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">configureDefaultServletHandling</span><span class="params">(configurer: <span class="type">DefaultServletHandlerConfigurer</span>?)</span></span> {</span><br><span class="line">        configurer?.enable()</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>RootConfig 使用<code>@ComponentScan</code>以实现对非 Web 组件的调用。</p><p>WebConfig 使用<code>@EnableWebMvc</code>启动 Spring MVC，使用<code>@ComponentScan</code>启用组件扫描，并继承 WebMvcConfigurerAdapter 重写其<code>configureDefaultServletHandling</code>方法以实现将静态资源的转发给默认 Servlet （貌似是多余的），同时添加一个 ViewResolver bean 查找对应的视图文件</p><h2 id="Spring-控制器"><a href="#Spring-控制器" class="headerlink" title="Spring 控制器"></a>Spring 控制器</h2><h3 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h3><p>在 Spring MVC 中，控制器是指添加了<code>@RequestMapping</code>注解的类，无论是在方法还是类上。控制器本身所带的<code>@Controller</code>则影响不大，如下面的代码。注解上的参数为数组，说明可以映射多个路径、多个请求类型。<code>home</code>返回的字符串将配合上面 WebConfig 的<code>viewResolver</code>方法寻找网页视图</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HomeController</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping(value = [<span class="meta-string">"/"</span>], method = [RequestMethod.GET])</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">home</span><span class="params">()</span></span>: String {</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"home"</span></span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>现在就可以通过配置 Tomcat 运行本地服务器检验项目了，首先需要下载 Tomcat 包并解压，在 IDEA 中<code>EDIT Configurations</code>，添加一个 local 的 Tomcat，添加完成之后大概如下</p><p><img src="/Spring-学习笔记（四）：构建-Web-应用程序/tomcat_server.png" alt></p><p>接下来还有最重要的一部，否则会出现一个很奇怪的错误</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">java.lang.ClassNotFoundException: org.springframework.web.context.ContextLoaderListener</span><br></pre></td></tr></tbody></table></figure><p>这是因为项目中通过 Maven 引入的库并没有被 Tomcat 识别，需要手动添加，如下图，点击左下角的编辑图标</p><p><img src="/Spring-学习笔记（四）：构建-Web-应用程序/tomcat_deploy.png" alt></p><p>在右侧的<code>Available Elements</code>中右键项目<code>spittr</code>，选择<code>Put into Output Root</code>，成功之后左侧的<code>WEB-INF/lib</code>下应该有需要的库</p><p><img src="/Spring-学习笔记（四）：构建-Web-应用程序/tomcat_put.jpg" alt></p><p>配置结束，需要注意的是不要导入 IDEA 生成的 Spring Web 框架，因为我们已经使用了 Maven 导入所有项目需要的类库，而且配置文件都是通过 Java 代码而没有引入任何的 XML 文件（除了 Maven 的<code>pom.xml</code>）。成功运行项目之后，浏览器打开<code>http://localhost:8080/spittr_war_exploded/</code>即可看到<code>home.jsp</code>中的网页视图</p><h3 id="传递数据到视图中"><a href="#传递数据到视图中" class="headerlink" title="传递数据到视图中"></a>传递数据到视图中</h3><p>首先建立数据模型，定义一条<code>Spittr</code>的内容为<code>Spittle</code>，同时通过一个仓库接口对外提供数据</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">data</span> <span class="class"><span class="keyword">class</span> <span class="title">Spittle</span></span>(<span class="keyword">val</span> id: <span class="built_in">Long</span>, <span class="keyword">val</span> message: String, <span class="keyword">val</span> time: Date, <span class="keyword">val</span> latitude: <span class="built_in">Double</span>, <span class="keyword">val</span> longitude: <span class="built_in">Double</span>)</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">SpittleRepository</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">findSpittles</span><span class="params">(max: <span class="type">Long</span>, count: <span class="type">Int</span>)</span></span>: List<Spittle></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SpittleRepositoryImpl</span>: <span class="type">SpittleRepository {</span></span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">findSpittles</span><span class="params">(max: <span class="type">Long</span>, count: <span class="type">Int</span>)</span></span>: List<Spittle> {</span><br><span class="line">        <span class="keyword">return</span> createSpittles(count)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">createSpittles</span><span class="params">(count: <span class="type">Int</span>)</span></span>: List<Spittle> {</span><br><span class="line">        <span class="keyword">val</span> spittles: MutableList<Spittle> = ArrayList()</span><br><span class="line">        <span class="keyword">for</span> (i <span class="keyword">in</span> <span class="number">0</span> until count) {</span><br><span class="line">            spittles.add(Spittle(id = i.toLong(), message = <span class="string">"This is spittle<span class="variable">$i</span>"</span>, time = Date(), latitude = Math.random(), longitude = Math.random()))</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span> spittles</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>配置对应的控制器 SpittleController</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping(<span class="meta-string">"/spittles"</span>)</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SpittleController</span></span></span><br><span class="line"><span class="meta">@Autowired</span> <span class="keyword">constructor</span>(<span class="keyword">private</span> <span class="keyword">val</span> spittleRepository: SpittleRepository) {</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping(method = [RequestMethod.GET])</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">spittles</span><span class="params">(model: <span class="type">Model</span>)</span></span>: String {</span><br><span class="line">        model.addAttribute(<span class="string">"spittleList"</span>, spittleRepository.findSpittles(<span class="built_in">Long</span>.MAX_VALUE, <span class="number">20</span>))</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"spittles"</span></span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>相应的 JSP 页面为<code>spittles.jsp</code></p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">%@</span> <span class="attr">taglib</span> <span class="attr">uri</span>=<span class="string">"http://java.sun.com/jsp/jstl/core"</span> <span class="attr">prefix</span>=<span class="string">"c"</span> %></span></span><br><span class="line"><span class="tag"><<span class="name">%@</span> <span class="attr">page</span> <span class="attr">contentType</span>=<span class="string">"text/html;charset=UTF-8"</span> <span class="attr">language</span>=<span class="string">"java"</span> %></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">title</span>></span>SpittleList<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">c:forEach</span> <span class="attr">items</span>=<span class="string">"${spittleList}"</span> <span class="attr">var</span>=<span class="string">"spittle"</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">li</span> <span class="attr">id</span>=<span class="string">"spittle_<c:out value="</span><span class="attr">spittle.id</span>"/></span>"></span><br><span class="line">            <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"spittleMessage"</span>></span></span><br><span class="line">                <span class="tag"><<span class="name">c:out</span> <span class="attr">value</span>=<span class="string">"${spittle.message}"</span>/></span></span><br><span class="line">            <span class="tag"></<span class="name">div</span>></span></span><br><span class="line">            <span class="tag"><<span class="name">div</span>></span></span><br><span class="line">                <span class="tag"><<span class="name">span</span> <span class="attr">lass</span>=<span class="string">"spittleTime"</span>></span></span><br><span class="line">                    <span class="tag"><<span class="name">c:out</span> <span class="attr">value</span>=<span class="string">"${spittle.time}"</span> /></span></span><br><span class="line">                <span class="tag"></<span class="name">span</span>></span></span><br><span class="line">                <span class="tag"><<span class="name">span</span> <span class="attr">class</span>=<span class="string">"spittleLocation"</span>></span></span><br><span class="line">                    (<span class="tag"><<span class="name">c:out</span> <span class="attr">value</span>=<span class="string">"${spittle.latitude}"</span> /></span></span><br><span class="line">                    <span class="tag"><<span class="name">c:out</span> <span class="attr">value</span>=<span class="string">"${spittle.longitude}"</span> /></span>)</span><br><span class="line">                <span class="tag"></<span class="name">span</span>></span></span><br><span class="line">            <span class="tag"></<span class="name">div</span>></span></span><br><span class="line">        <span class="tag"></<span class="name">li</span>></span></span><br><span class="line">    <span class="tag"></<span class="name">c:forEach</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></tbody></table></figure><p>需要注意的是，使用 JSTL 需要通过 Maven 引入两个库，参考<a href="https://howtodoinjava.com/spring-mvc/how-to-add-jstl-support-in-spring-3-using-maven/" target="_blank" rel="noopener">这里</a>——最重要的一步，将下载好之后的类库添加进 Tomcat lib 文件夹中</p><p>运行项目之后在浏览器打开<code>http://localhost:8080/spittr_war_exploded/spittles</code>即可查看效果</p><h2 id="接受请求输入"><a href="#接受请求输入" class="headerlink" title="接受请求输入"></a>接受请求输入</h2><p>Spring MVC 允许多种方式将客户端中的数据传送到控制器的处理方法中</p><ul><li>查询参数（Query Parameter）</li><li>表单参数（Form Parameter）</li><li>路径变量（Path Parameter）</li></ul><h3 id="处理查询参数"><a href="#处理查询参数" class="headerlink" title="处理查询参数"></a>处理查询参数</h3><p>添加一个新功能，让用户可以查看某一页的 Spittle 历史，为此需要获得一个 Spittle 的 ID——其恰好小于当前页最后一条 Spittle 的 ID。修改 SpittleController 中的方法 </p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(method = [RequestMethod.GET])</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">spittles</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">        <span class="meta">@RequestParam(<span class="meta-string">"max"</span>, defaultValue = Long.MAX_VALUE.toString()</span>)</span></span> max: <span class="built_in">Long</span>,</span><br><span class="line">        <span class="meta">@RequestParam(<span class="meta-string">"count"</span>, defaultValue = <span class="meta-string">"20"</span>)</span> count: <span class="built_in">Int</span>,</span><br><span class="line">        model: Model): String {</span><br><span class="line">    model.addAttribute(<span class="string">"spittleList"</span>, spittleRepository.findSpittles(max, count))</span><br><span class="line">    <span class="keyword">return</span> <span class="string">"spittles"</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>现在客户端可以通过再链接后添加可选的查询参数获得不同的结果，比如<code>http://localhost:8080/spittr_war_exploded/spittles?count=5</code>将显示 5 条 Spittle</p><h3 id="处理路径参数"><a href="#处理路径参数" class="headerlink" title="处理路径参数"></a>处理路径参数</h3><p>假设应用程序需要根据给定的 ID 展现某一条 Spittle 记录，从面向资源的角度，这时 Spittle 应该通过 URL 路径标示，而不是通过查询参数，控制器添加新的方法，同时 SpittleRepository 添加一个查询单个 Spittle 的新接口</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@RequestMapping(value = [<span class="meta-string">"/{spittleId}"</span>])</span></span><br><span class="line"> <span class="function"><span class="keyword">fun</span> <span class="title">showSpittle</span><span class="params">(<span class="meta">@PathVariable</span> spittleId: <span class="type">Long</span>, model: <span class="type">Model</span> )</span></span>: String {</span><br><span class="line">     model.addAttribute(<span class="string">"spittle"</span>, spittleRepository.findSpittle(spittleId))</span><br><span class="line">     <span class="keyword">return</span> <span class="string">"spittle"</span></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">findSpittle</span><span class="params">(id: <span class="type">Long</span>)</span></span>: Spittle {</span><br><span class="line">     <span class="keyword">return</span> Spittle(id = id, message = <span class="string">"This is spittle<span class="variable">$id</span>"</span>, time = Date(), latitude = Math.random(), longitude = Math.random())</span><br><span class="line"> }</span><br></pre></td></tr></tbody></table></figure><p>运行项目，现在就可以通过在原有路径上添加<code>/id</code>的方式访问特定的 Spittle，比如<code>http://localhost:8080/spittr_war_exploded/spittles/9527</code></p><p><img src="/Spring-学习笔记（四）：构建-Web-应用程序/spittle_by_id.png" alt></p><h3 id="处理表单"><a href="#处理表单" class="headerlink" title="处理表单"></a>处理表单</h3><p>为 Spittr 提供用户注册和通过<code>username</code>查看个人信息的接口，为此首先要定义一个用户类型 Spitter 以及对应的 Repository。需要注意的是，使用 Kotlin 的<code>data class</code>时，属性要添加默认值并且要使用<code>var</code>而不能用<code>val</code>，不符合其中一个条件，提交表格时 Spring 都不能实例化 Spitter。同时还引入了<code>javax.validation.validation-api</code>包进行错误检验</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">data</span> <span class="class"><span class="keyword">class</span> <span class="title">Spitter</span></span>(<span class="meta">@NotNull</span> <span class="meta">@Size(min = 3, max = 16)</span> <span class="keyword">var</span> firstName: String = <span class="string">""</span>,</span><br><span class="line">              <span class="meta">@NotNull</span> <span class="meta">@Size(min = 3, max = 16)</span> <span class="keyword">var</span> lastName: String = <span class="string">""</span>,</span><br><span class="line">              <span class="meta">@NotNull</span> <span class="meta">@Size(min = 3, max = 16)</span> <span class="keyword">var</span> username: String = <span class="string">""</span>,</span><br><span class="line">              <span class="meta">@NotNull</span> <span class="meta">@Size(min = 3, max = 16)</span> <span class="keyword">var</span> password: String = <span class="string">""</span>)</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">SpitterRepository</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">save</span><span class="params">(spitter: <span class="type">Spitter</span>)</span></span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">findByUsername</span><span class="params">(username: <span class="type">String</span>)</span></span>: Spitter?</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SpitterRepositoryImpl</span>: <span class="type">SpitterRepository {</span></span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> spitterMap: MutableMap<String, Spitter> = HashMap()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">save</span><span class="params">(spitter: <span class="type">Spitter</span>)</span></span> {</span><br><span class="line">        spitterMap[spitter.username] = spitter</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">findByUsername</span><span class="params">(username: <span class="type">String</span>)</span></span>: Spitter? = spitterMap[username]</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>添加一个控制器 SpitterController，实现下面的逻辑：用户打开<code>/spitter/register</code>页面时显示注册页面，其中包含一个表单。提交注册信息之后，判断注册信息有误错误，正确则重定向至<code>/spitter/username</code>路径，展示 Profile 信息</p><p><em>奇怪的是当表单参数不符合注解要求时并不会触发 Error，尚不知道原因，已排除 Kotlin 代码的问题</em></p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Controller</span></span><br><span class="line"><span class="meta">@RequestMapping(<span class="meta-string">"/spitter"</span>)</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SpitterController</span></span></span><br><span class="line"><span class="meta">@Autowired</span> <span class="keyword">constructor</span>(<span class="keyword">private</span> <span class="keyword">val</span> spitterRepository: SpitterRepository) {</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping(value = [<span class="meta-string">"/register"</span>], method = [RequestMethod.GET])</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">showRegistrationForm</span><span class="params">()</span></span>: String = <span class="string">"registerForm"</span></span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping(value = [<span class="meta-string">"/register"</span>], method = [RequestMethod.POST])</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">processRegistration</span><span class="params">(<span class="meta">@Validated</span> spitter: <span class="type">Spitter</span>, errors: <span class="type">Errors</span>)</span></span>: String {</span><br><span class="line">        <span class="keyword">if</span> (errors.hasErrors()) {</span><br><span class="line">            <span class="keyword">return</span> <span class="string">"registerForm"</span></span><br><span class="line">        }</span><br><span class="line">        spitterRepository.save(spitter)</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"redirect:/spitter/<span class="subst">${spitter.username}</span>"</span></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping(value = [<span class="meta-string">"/{username}"</span>], method = [RequestMethod.GET])</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">showSpitterProfile</span><span class="params">(<span class="meta">@PathVariable</span> username: <span class="type">String</span>, model: <span class="type">Model</span>)</span></span>: String {</span><br><span class="line">        <span class="keyword">val</span> spitter = spitterRepository.findByUsername(username)</span><br><span class="line">        model.addAttribute(<span class="string">"spitter"</span>, spitter)</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"profile"</span></span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>需要准备两个 View 文件，<code>registerForm.jsp</code>和<code>profile.jsp</code>。第一个文件如下</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">%@</span> <span class="attr">page</span> <span class="attr">contentType</span>=<span class="string">"text/html;charset=UTF-8"</span> <span class="attr">language</span>=<span class="string">"java"</span> %></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">title</span>></span>Spitter<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">h1</span>></span>Register<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">form</span> <span class="attr">method</span>=<span class="string">"post"</span> <span class="attr">action</span>=<span class="string">"register"</span>></span></span><br><span class="line">        First Name: <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">name</span>=<span class="string">"firstName"</span>></span><span class="tag"><<span class="name">br</span>></span></span><br><span class="line">        Last Name: <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">name</span>=<span class="string">"lastName"</span>></span><span class="tag"><<span class="name">br</span>></span></span><br><span class="line">        User Name: <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"text"</span> <span class="attr">name</span>=<span class="string">"username"</span>></span><span class="tag"><<span class="name">br</span>></span></span><br><span class="line">        Password: <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"password"</span> <span class="attr">name</span>=<span class="string">"password"</span>></span><span class="tag"><<span class="name">br</span>></span></span><br><span class="line"></span><br><span class="line">        <span class="tag"><<span class="name">input</span> <span class="attr">type</span>=<span class="string">"submit"</span> <span class="attr">value</span>=<span class="string">"Register"</span>></span></span><br><span class="line">    <span class="tag"></<span class="name">form</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></tbody></table></figure><p>第二个文件为</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">%@</span> <span class="attr">taglib</span> <span class="attr">prefix</span>=<span class="string">"c"</span> <span class="attr">uri</span>=<span class="string">"http://java.sun.com/jsp/jstl/core"</span> %></span></span><br><span class="line"><span class="tag"><<span class="name">%@</span> <span class="attr">page</span> <span class="attr">contentType</span>=<span class="string">"text/html;charset=UTF-8"</span> <span class="attr">language</span>=<span class="string">"java"</span> %></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">title</span>></span>Profile<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">h1</span>></span>Spitter Profile<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">c:out</span> <span class="attr">value</span>=<span class="string">"Username is ${spitter.username}"</span> /></span><span class="tag"><<span class="name">br</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">c:out</span> <span class="attr">value</span>=<span class="string">"First name is ${spitter.firstName} "</span> /></span></span><br><span class="line">    <span class="tag"><<span class="name">c:out</span> <span class="attr">value</span>=<span class="string">"and last name is ${spitter.lastName}"</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></tbody></table></figure><p>运行项目即可检验开发成果</p><hr><p>最后再给出主页视图的文件<code>home.jsp</code>，关于本章的内容基本就是这样</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">%@</span> <span class="attr">taglib</span> <span class="attr">prefix</span>=<span class="string">"c"</span> <span class="attr">uri</span>=<span class="string">"http://java.sun.com/jsp/jstl/core"</span> %></span></span><br><span class="line"><span class="tag"><<span class="name">%@</span> <span class="attr">page</span> <span class="attr">contentType</span>=<span class="string">"text/html;charset=UTF-8"</span> <span class="attr">language</span>=<span class="string">"java"</span> %></span></span><br><span class="line"><span class="tag"><<span class="name">html</span>></span></span><br><span class="line"><span class="tag"><<span class="name">head</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">title</span>></span>Spittr<span class="tag"></<span class="name">title</span>></span></span><br><span class="line"><span class="tag"></<span class="name">head</span>></span></span><br><span class="line"><span class="tag"><<span class="name">body</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">h1</span>></span>Welcome to Spittr<span class="tag"></<span class="name">h1</span>></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"<c:url value="</span>/<span class="attr">spittles</span>" /></span>">Spittles<span class="tag"></<span class="name">a</span>></span> |</span><br><span class="line">    <span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"<c:url value="</span>/<span class="attr">spitter</span>/<span class="attr">register</span>" /></span>">Register<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"><span class="tag"></<span class="name">body</span>></span></span><br><span class="line"><span class="tag"></<span class="name">html</span>></span></span><br></pre></td></tr></tbody></table></figure><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;Spring MVC 基于模型-视图-控制器（ Model-View-Controller，MVC ）模式实现，用以构建灵活和松耦合的 Web 应用程序。本文将通过一个社交网站&lt;code&gt;Spittr&lt;/code&gt;的实例，介绍 Spring MVC 框架，并在其基础上构建处
      
    
    </summary>
    
      <category term="Spring" scheme="http://yoursite.com/categories/Spring/"/>
    
    
      <category term="Spring" scheme="http://yoursite.com/tags/Spring/"/>
    
      <category term="Web" scheme="http://yoursite.com/tags/Web/"/>
    
  </entry>
  
  <entry>
    <title>Spring 学习笔记（三）：面向切面</title>
    <link href="http://yoursite.com/Spring-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%89%EF%BC%89%EF%BC%9A%E9%9D%A2%E5%90%91%E5%88%87%E9%9D%A2/"/>
    <id>http://yoursite.com/Spring-学习笔记（三）：面向切面/</id>
    <published>2019-06-02T04:52:24.000Z</published>
    <updated>2019-06-03T04:18:04.000Z</updated>
    
    <content type="html"><![CDATA[<p><a href="https://febers.github.io/Spring-学习笔记（一）：基本理念和-Bean-装配/" target="_blank" rel="noopener">第一篇笔记</a> 曾简单提及了 AOP 的知识，本文将重点展开 Spring 对切面的支持，包括如何使用和 AspectJ 的具体应用<a id="more"></a></p><h2 id="面向切面编程"><a href="#面向切面编程" class="headerlink" title="面向切面编程"></a>面向切面编程</h2><p>Aspect-Oriented Programming，AOP。每个应用除了其核心业务之外，还会需要一些模块化的通用功能——比如日志、安全、通知等。重用通用功能的方式一般有继承（inheritance）、 委托（delegation），缺点在于前者会导致对象体系复杂、难以维护，后者则可能需要对委托对象进行复杂的调用。切面提供了不一样的思路，并且在很多场景下更加清晰简洁。</p><h3 id="AOP-术语"><a href="#AOP-术语" class="headerlink" title="AOP 术语"></a>AOP 术语</h3><p><img src="/Spring-学习笔记（三）：面向切面/AOP_1.jpg" alt></p><h4 id="Advice"><a href="#Advice" class="headerlink" title="Advice"></a>Advice</h4><p>通知，如果翻译为“增强”就能更直观地理解它所扮演的角色。Spring 切面可以应用 5 种类型的通知</p><ul><li>前置通知（ Before ）：在目标方法被调用之前调用通知功能</li><li>后置通知（ After ）：在目标方法完成之后调用通知，不关心方法的返回值</li><li>返回通知（ After-returning ）：在目标方法成功执行之后调用通知</li><li>异常通知（ After-throwing ）：在目标方法抛出异常之后调用通知</li><li>环绕通知（ Around ）：通知包裹了被通知的方法，在被通知的方法调用之前喝调用之后执行自定义的行为</li></ul><h4 id="Joint-point"><a href="#Joint-point" class="headerlink" title="Joint point"></a>Joint point</h4><p>连接点，应用可能有数以千计的时机应用通知，这些时机被称为连接点。根据上面的图理解，连接点是在应用执行过程中能够插入切面的一个点，改点可以是调用方法时、抛出异常时、甚至修改一个字段时。</p><h4 id="Pointcut"><a href="#Pointcut" class="headerlink" title="Pointcut"></a>Pointcut</h4><p>切点，通知定义了切面的“什么”和“何时”，连接点定义了“时间点”，切点则定义了“何处”。切点会匹配通知所要织入的一个或多个连接点。通常使用明确的类和方法名称、或者使用正则表达式定义所匹配的类和方法来指定切点，有些 AOP 框架也会允许动态创建切点。</p><h4 id="Aspect"><a href="#Aspect" class="headerlink" title="Aspect"></a>Aspect</h4><p>切面，通知+切点，这两者共同定义了切面的全部内容</p><h4 id="Introduction"><a href="#Introduction" class="headerlink" title="Introduction"></a>Introduction</h4><p>引入，允许开发者向现有的类添加方法或属性</p><h4 id="Weaving"><a href="#Weaving" class="headerlink" title="Weaving"></a>Weaving</h4><p>织入，把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中，在目标对象的生命周期里有多个点可以进行织入：</p><ul><li>编译期：切面在目标类编译时被织入。需要特殊的编译器，如 AspectJ 的织入编译器</li><li>类加载期：切面在目标类加载到 JVM 时被织入。这种方式需要特殊的类加载器，从而在目标类被引用之前增强其字节码。比如 AspectJ 5 的加载时织入（load-time weaving，LTW）</li><li>运行期：切面在应用运行的某个时刻被织入。一般情况下，织入时 AOP 容器会为目标对象动态地创建一个代理对象。比如 Spring AOP</li></ul><h3 id="Spring-AOP"><a href="#Spring-AOP" class="headerlink" title="Spring AOP"></a>Spring AOP</h3><p>Spring 提供了 4 中类型的 AOP 支持：</p><ul><li>基于代理的经典 Spring AOP</li><li>纯 POJO 切面</li><li><code>@AspectJ</code>注解驱动的切面</li><li>注入式 AspectJ 切面</li></ul><p>相比起其他方式，第一种显得笨重而古典，不再介绍。纯 POJO 切面需要借助 Spring 的 <code>aop</code>命名空间，虽然足够简便，但也不再赘述。Spring 借鉴了 AspectJ 的切面以提供注解驱动的 AOP，其本质仍然是代理，但是编程模型几乎与成熟的 AspectJ 注解完全一致。如果开发者对 AOP 的需求超过了简单的方法调用（如构造器或属性拦截），那么可以考虑第四种方式——使用 AspectJ。</p><p>通过再代理类中包裹切面，Spring 在运行期把切面织入到 Spring 管理的 bean 中。代理类封装了目标类并拦截被通知方法的调用，再把调用转发给真正的目标 bean</p><p><img src="/Spring-学习笔记（三）：面向切面/AOP_2.jpg" alt></p><p>由于 Spring  AOP 基于动态代理，所以只支持方法连接点，不支持字段连接点——无法创建细粒度的通知、不支持构造器连接点——无法在 bean 创建时应用通知。</p><h2 id="通过切点选择连接点"><a href="#通过切点选择连接点" class="headerlink" title="通过切点选择连接点"></a>通过切点选择连接点</h2><p>使用 AspectJ 的切点表达式语言来定义切点。Spring 仅支持 AspectJ 切点指示器的一个子集，因为 Spring 基于代理，而某些切点表达式与代理无关。下表是 Spring AOP 所支持的指示器</p><table><thead><tr><th>AspectJ 指示器</th><th>描述</th></tr></thead><tbody><tr><td>arg()</td><td>限制连接点匹配参数为指定类型的执行方法</td></tr><tr><td>@args()</td><td>限制连接点匹配参数由指定注解标注的执行方法</td></tr><tr><td>execution()</td><td>用于匹配是连接点的执行方法</td></tr><tr><td>this()</td><td>限制连接点匹配 AOP 代理的 bean 引用为指定类型的类</td></tr><tr><td>target()</td><td>限制连接点匹配目标对象为指定类型的类</td></tr><tr><td>@target()</td><td>限制连接点匹配特定的具有指定类型注解的执行对象</td></tr><tr><td>within()</td><td>限制连接点匹配指定的类型</td></tr><tr><td>@within()</td><td>限制连接点匹配指定注解所标注的类型（方法定义在该类型中）</td></tr><tr><td>@annotation</td><td>限定匹配带有指定注解的连接点</td></tr></tbody></table><p>尝试使用其他指示器时，将抛出<code>IllegalArgumentException</code>异常</p><p>以上的指示器只有<code>execution()</code>指示器是实际执行匹配的，其他指示器都是用来限制匹配的。</p><h3 id="编写切点"><a href="#编写切点" class="headerlink" title="编写切点"></a>编写切点</h3><p>准备一个主题 Performance，可代表任何类型的现场表演，假设要编写其中<code>perform</code>方法触发的通知</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">package</span> concert</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">Performance</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">perform</span><span class="params">()</span></span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>使用 AspectJ 表达式编写切点</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line">execution(* concert.Performance.perform(..)) && within(concert.*)</span><br></pre></td></tr></tbody></table></figure><p>使用<code>execution()</code>指示器选择<code>perform</code>方法，方法表达式以<code>*</code>开始表示不关心返回值类型，然后使用全限定类名和方法名；使用两个点号<code>..</code>表示不关心方法参数列表，切点会选择为所有的<code>perform</code>方法。<code>&&</code>操作符把<code>execution()</code>和<code>within</code>指示器连接在一起形成<code>与（and）</code>关系，限制需要配置的切点仅匹配<code>concert</code>包，类似也可以使用<code>||</code>和<code>!</code>来标识<code>或（or）</code>和<code>非（not）</code>操作</p><p>当然也可以通过 Spring 引入的指示器<code>bean()</code>指示特定的 bean，参数为 bean 的 id</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line">execution(* concert.Performance.perform(..)) && bean(<span class="string">'woodstock'</span>)</span><br><span class="line">execution(* concert.Performance.perform(..)) && !bean(<span class="string">'woodstock'</span>)</span><br></pre></td></tr></tbody></table></figure><h2 id="使用注解创建切面"><a href="#使用注解创建切面" class="headerlink" title="使用注解创建切面"></a>使用注解创建切面</h2><h3 id="简单通知"><a href="#简单通知" class="headerlink" title="简单通知"></a>简单通知</h3><p>对于一场演出，我们将“观众”定义为切面</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Audience</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before(<span class="meta-string">"execution(** concert.Performance.perform(..)"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">silenceCellPhones</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Silencing cell phones"</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before(<span class="meta-string">"execution(** concert.Performance.perform(..)"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">takeSeats</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Taking seats"</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AfterReturning(<span class="meta-string">"execution(** concert.Performance.perform(..)"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">applause</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"CLAP CLAP CLAP!!"</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AfterThrowing(<span class="meta-string">"execution(** concert.Performance.perform(..)"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">demandRefund</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Demanding a refund"</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><blockquote><p>AspectJ 库需要通过依赖引入</p></blockquote><p>Audience 定义了四个方法，通过 AOP 注解，形成以下期望的行为——演出之前观众就坐、将手机静音，演出很精彩则鼓掌欢呼，演出没有达到预期则要求退款。以上的方式有一点不足，每个方法的切点表达式都是一样的，重复了四次。为此可以使用<code>@Pointcut</code>注解定义可重用的切点</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Audience</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Pointcut(<span class="meta-string">"execution(** concert.Performance.perform(..))"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">performance</span><span class="params">()</span></span>{ }</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Before(<span class="meta-string">"performance()"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">silenceCellPhones</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Silencing cell phones"</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Before(<span class="meta-string">"performance()"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">takeSeats</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Taking seats"</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AfterReturning(<span class="meta-string">"performance()"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">applause</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"CLAP CLAP CLAP!!"</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@AfterThrowing(<span class="meta-string">"performance()"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">demandRefund</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Demanding a refund"</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>除了注解和作为标识的空<code>performance</code>方法，Audience 仍然是一个 POJO，可以添加<code>@Component</code>注解将其注入容器中。但此时 Audience 不会视为切面，需要在配置类的的类级别通过使用<code>@EnableAspectJAutoProxy</code>注解启动自动代理功能</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAspectJAutoProxy</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">ConcertConfig</span></span></span><br></pre></td></tr></tbody></table></figure><p>同时为 Performance 提供一个实现类</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">MyPerformance</span>: <span class="type">Performance {</span></span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">perform</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Start perform"</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>测试类如下</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner::class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = [ConcertConfig::class])</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PerformTest</span> </span>{</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">lateinit</span> <span class="keyword">var</span> performance: Performance</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">perform</span><span class="params">()</span></span> {</span><br><span class="line">        performance.perform()</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出结果为</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Silencing cell phones</span><br><span class="line">Taking seats</span><br><span class="line">Start perform</span><br><span class="line">CLAP CLAP CLAP!!</span><br></pre></td></tr></tbody></table></figure><h3 id="环绕通知"><a href="#环绕通知" class="headerlink" title="环绕通知"></a>环绕通知</h3><p>环绕通知时最强大的通知类型，能够使编写的逻辑将被通知的目标方法完全包装，就像 在一个通知方法中同时编写前置通知和后置通知，重写上面的 Audience 切面</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Audience</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Pointcut(<span class="meta-string">"execution(** concert.Performance.perform(..))"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">performance</span><span class="params">()</span></span>{ }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Around(<span class="meta-string">"performance()"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">watchPerformance</span><span class="params">(jointPoint: <span class="type">ProceedingJoinPoint</span>)</span></span> {</span><br><span class="line">        <span class="keyword">try</span> {</span><br><span class="line">            println(<span class="string">"Silencing cell phones"</span>)</span><br><span class="line">            println(<span class="string">"Taking seats"</span>)</span><br><span class="line">            jointPoint.proceed()</span><br><span class="line">            println(<span class="string">"CLAP CLAP CLAP!!"</span>)</span><br><span class="line">        } <span class="keyword">catch</span> (e: Exception) {</span><br><span class="line">            println(<span class="string">"Demanding a refund"</span>)</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>上面的代码将实现同样的功能。环绕通知注解的方法<code>watchPerformance</code>中类型为<code>ProceedingJoinPoint</code>的参数是必须的，由此拿到切点的引用</p><h3 id="带参数的通知"><a href="#带参数的通知" class="headerlink" title="带参数的通知"></a>带参数的通知</h3><p>通过一个例子来展现带参数的通知如何实现，首先修改 MyPerformance 类</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">MyPerformance</span>: <span class="type">Performance {</span></span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">var</span> performList: List<String> = arrayListOf(<span class="string">"Song"</span>, <span class="string">"Dance"</span>, <span class="string">"Magic"</span>)</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">perform</span><span class="params">()</span></span> {</span><br><span class="line">        perform(Math.round(<span class="number">10f</span>) % <span class="number">2</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">perform</span><span class="params">(type: <span class="type">Int</span>)</span></span> {</span><br><span class="line">        println(<span class="string">"Start perform: <span class="subst">${performList[type]}</span>"</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>假设现在有一个计数类 PerformanceCounter，其将跟踪每一次表演节目，记录下该节目的类型以及次数并打印</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Aspect</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PerformanceCounter</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">var</span> playCounter: MutableMap<<span class="built_in">Int</span>, <span class="built_in">Int</span>> = HashMap()</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Pointcut(<span class="meta-string">"execution(** concert.Performance.perform(..)) && args(whichType)"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">performanceTypePlay</span><span class="params">(whichType: <span class="type">Int</span>)</span></span> { }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@After(<span class="meta-string">"performanceTypePlay(typeIntValue)"</span>)</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">countPerformance</span><span class="params">(typeIntValue: <span class="type">Int</span>)</span></span> {</span><br><span class="line">        <span class="keyword">val</span> currentCount = getPlayCount(typeIntValue)</span><br><span class="line">        playCounter[typeIntValue] = currentCount+<span class="number">1</span></span><br><span class="line">        println(<span class="string">"=== type: <span class="subst">${getDesByInt(typeIntValue)}</span>, played <span class="subst">${currentCount+<span class="number">1</span>}</span> times ==="</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">getPlayCount</span><span class="params">(type: <span class="type">Int</span>)</span></span>: <span class="built_in">Int</span> = playCounter[type] ?: <span class="number">0</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="title">getDesByInt</span><span class="params">(type: <span class="type">Int</span>)</span></span>: String = <span class="keyword">when</span>(type) {</span><br><span class="line">        <span class="number">0</span> -> <span class="string">"Song"</span></span><br><span class="line">        <span class="number">1</span> -> <span class="string">"Dance"</span></span><br><span class="line">        <span class="number">2</span> -> <span class="string">"Magic"</span></span><br><span class="line">        <span class="keyword">else</span> -> <span class="string">"null"</span></span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>着重注意不同方法中的<code>type</code>参数，参数名称各有不同，但本质都代表“表演类型”。而相同的参数名称则表示一一对应的关系：定义切点时，AspectJ 表达式内使用的参数名称为<code>whichType</code>，与标识该切点的空函数的参数是一致的；通知方法上的参数名称<code>typeIntValue</code>则与通知时机中引用切点时 AspectJ 表达式内的参数名称一致</p><p>测试类如下</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner::class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = [ConcertConfig::class])</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PerformTest</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">lateinit</span> <span class="keyword">var</span> performance: Performance</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">perform</span><span class="params">()</span></span> {</span><br><span class="line">        <span class="keyword">for</span> (i <span class="keyword">in</span> <span class="number">0</span>..<span class="number">4</span>) {</span><br><span class="line">            performance.perform(i % <span class="number">3</span>)</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出为</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Start perform: Song</span><br><span class="line">=== <span class="built_in">type</span>: Song, played 1 <span class="built_in">times</span> ===</span><br><span class="line">Start perform: Dance</span><br><span class="line">=== <span class="built_in">type</span>: Dance, played 1 <span class="built_in">times</span> ===</span><br><span class="line">Start perform: Magic</span><br><span class="line">=== <span class="built_in">type</span>: Magic, played 1 <span class="built_in">times</span> ===</span><br><span class="line">Start perform: Song</span><br><span class="line">=== <span class="built_in">type</span>: Song, played 2 <span class="built_in">times</span> ===</span><br><span class="line">Start perform: Dance</span><br><span class="line">=== <span class="built_in">type</span>: Dance, played 2 <span class="built_in">times</span> ===</span><br></pre></td></tr></tbody></table></figure><h2 id="注入-AspectJ-切面"><a href="#注入-AspectJ-切面" class="headerlink" title="注入 AspectJ 切面"></a>注入 AspectJ 切面</h2><p>AspectJ 和 Spring 实际上是独立的，只不过 Spring AOP 借助了前者的指示器。通过一个例子展示如何注入原始的 AspectJ 切面。</p><p>首先准备一个评论员，在表演之后发表一段言论，其类型为<code>aspect</code>，在 IDEA 中可通过右键 New -> Aspect 新建一个该类型的文件</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> aspect CriticAspect {</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">CriticAspect</span><span class="params">()</span> </span>{ }</span><br><span class="line"></span><br><span class="line">    <span class="function">pointcut <span class="title">performance</span><span class="params">()</span>: <span class="title">execution</span><span class="params">(* concert.MyPerformance.perform(..)</span>)</span>;</span><br><span class="line"></span><br><span class="line">    after(): performance() {</span><br><span class="line">        System.out.println(<span class="string">"something"</span>);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在 Spring 框架中，CriticAspect 将不会由容器创建，因为它属于 AspectJ 切面，由 AspectJ 在运行时创建。所以需要通过 AspectJ 切面提供的静态<code>aspectOf</code>方法给 Spring 返回切面的单例，Spring XML 配置写成以下形式</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">...</span>></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"concert"</span> /></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">aop:aspectj-autoproxy</span> /></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"concert.CriticismEngineImpl"</span> <span class="attr">id</span>=<span class="string">"criticismEngine"</span> /></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"concert.MyPerformance"</span> <span class="attr">id</span>=<span class="string">"performance"</span> /></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">bean</span> <span class="attr">class</span>=<span class="string">"concert.CriticAspect"</span> <span class="attr">factory-method</span>=<span class="string">"aspectOf"</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></tbody></table></figure><p>如果是 JavaConfig，则需要以下形式</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@EnableAspectJAutoProxy</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">ConcertConfig</span> </span>{</span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">criticAspect</span><span class="params">()</span></span>: concert.CriticAspect = org.aspectj.lang.Aspects.aspectOf(CriticAspect::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>但是运行时 JRE 将不会识别类 CriticAspect，无法运行。目前未找到解决办法，故使用 XML 配置的方法</p><p>之后最重要的一步是使用<code>ajc</code>编译器，以编译<code>.aj</code>文件。通过一个插件<a href="https://www.mojohaus.org/aspectj-maven-plugin/usage.html" target="_blank" rel="noopener"> Mojo’s AspectJ Maven Plugin</a> 引入<code>ajc</code>，注意此时 IDEA Compiler 的选项截图为</p><p><img src="/Spring-学习笔记（三）：面向切面/Compiler.png" alt></p><p>测试类如下</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner.class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(locations = {<span class="meta-string">"classpath:concert.xml"</span>})</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">PerformTest</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    Performance performance;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="keyword">public</span> void test() {</span><br><span class="line">        performance.perform();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>按理说应该运行成功，然而此时还是报错</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Error:ajc: can<span class="string">'t find critical required type java.io.Serializable</span></span><br><span class="line"><span class="string">Error:ajc: can'</span>t determine whether missing <span class="built_in">type</span> java.io.Serializable is an instance of concert.CriticAspect</span><br><span class="line">......</span><br><span class="line">Error:ajc: can<span class="string">'t find critical required type java.lang.Cloneable</span></span><br><span class="line"><span class="string">Error:ajc: can'</span>t determine whether missing <span class="built_in">type</span> java.lang.Cloneable is an instance of concert.CriticAspect</span><br><span class="line">......</span><br></pre></td></tr></tbody></table></figure><p>目前并无解决办法， V2EX 上的求助帖为 <a href="https://www.v2ex.com/t/570139#reply11" target="_blank" rel="noopener">诚心求助 Spring 注入式 AspectJ 切面时 ClassNotFoundException 的问题</a></p><p>Spring AOP 的内容到此为止，日后大概率会对本篇文章进行增删查改，继续进行下一步的学习吧</p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;&lt;a href=&quot;https://febers.github.io/Spring-学习笔记（一）：基本理念和-Bean-装配/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;第一篇笔记&lt;/a&gt; 曾简单提及了 AOP 的知识，本文将重点展开 Spring 对切面的支持，包括如何使用和 AspectJ 的具体应用&lt;/p&gt;
    
    </summary>
    
      <category term="Spring" scheme="http://yoursite.com/categories/Spring/"/>
    
    
      <category term="Spring" scheme="http://yoursite.com/tags/Spring/"/>
    
      <category term="AOP" scheme="http://yoursite.com/tags/AOP/"/>
    
  </entry>
  
  <entry>
    <title>Spring 学习笔记（二）：高级装配</title>
    <link href="http://yoursite.com/Spring-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%BA%8C%EF%BC%89%EF%BC%9A%E9%AB%98%E7%BA%A7%E8%A3%85%E9%85%8D/"/>
    <id>http://yoursite.com/Spring-学习笔记（二）：高级装配/</id>
    <published>2019-06-01T08:17:51.000Z</published>
    <updated>2019-06-02T05:03:48.000Z</updated>
    
    <content type="html"><![CDATA[<p>开发软件的过程中要涉及到不同的环境，需要配置不同的数据库配置、加密算法和外部环境的集成等组件，为此必须要考虑不同的环境下对应不同的配置。本文将从 Spring profile 出发，进而介绍条件化的 Bean 声明、自动装配的歧义性和 Bean 的作用域，以及 Spring 表达式语言。<a id="more"></a></p><h2 id="Spring-profile"><a href="#Spring-profile" class="headerlink" title="Spring profile"></a>Spring profile</h2><h3 id="配置-profile"><a href="#配置-profile" class="headerlink" title="配置 profile"></a>配置 profile</h3><p>使用注解<code>@Profile</code>指定某个 Bean 属于哪一个 profile，以不同环境下的数据库 Bean 为例</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">DataSourceConfig</span> </span>{</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(<span class="meta-string">"dev"</span>)</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">embeddedDataSource</span><span class="params">()</span></span>: DataSource = EmbeddedDatabaseBuilder()</span><br><span class="line">            .setType(EmbeddedDatabaseType.H2)</span><br><span class="line">            .addScript(<span class="string">"classpath:schema.sql"</span>)</span><br><span class="line">            .build()</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Profile(<span class="meta-string">"prod"</span>)</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">jndiDataSource</span><span class="params">()</span></span>: DataSource {</span><br><span class="line">        <span class="keyword">val</span> jndiObjectFactoryBean = JndiObjectFactoryBean()</span><br><span class="line">        jndiObjectFactoryBean.jndiName = <span class="string">"jndi/myDS"</span></span><br><span class="line">        jndiObjectFactoryBean.isResourceRef = <span class="literal">true</span></span><br><span class="line">        jndiObjectFactoryBean.setProxyInterface(javax.sql.DataSource::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span></span><br><span class="line">        <span class="keyword">return</span> jndiObjectFactoryBean.`<span class="keyword">object</span>` <span class="keyword">as</span> DataSource</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>同样支持使用 XML 文件配置 profile</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xmlns:jdbc</span>=<span class="string">"http://www.springframework.org/schema/jdbc"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">profile</span>=<span class="string">"dev"</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">jdbc:embedded-database</span> <span class="attr">id</span>=<span class="string">"dataSource2"</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">jdbc:script</span> <span class="attr">location</span>=<span class="string">"classpath:schema.sql"</span> /></span></span><br><span class="line">    <span class="tag">< /<span class="attr">jdbc:embedded-database</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></tbody></table></figure><p>可以将  profile 设置为<code>prod</code>，创建适用于生产环境的 JNDI 获取的 DataSource Bean，所有的配置文件都会被放进部署单元之中（如 WAR 文件），但是只有 profile 属性与当前激活 profile 相匹配的配置文件才会被使用。</p><p>不过更方便的方法是把所有的 Bean 放进同一个配置文件中</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span> <span class="attr">xmlns:jdbc</span>=<span class="string">"http://www.springframework.org/schema/jdbc"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:jee</span>=<span class="string">"http://www.springframework.org/schema/jee"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee.xsd"</span>></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">beans</span> <span class="attr">profile</span>=<span class="string">"dev"</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">jdbc:embedded-database</span> <span class="attr">id</span>=<span class="string">"dataSource"</span>></span></span><br><span class="line">            <span class="tag"><<span class="name">jdbc:script</span> <span class="attr">location</span>=<span class="string">"classpath:schema.sql"</span> /></span></span><br><span class="line">        <span class="tag"></<span class="name">jdbc:embedded-database</span>></span></span><br><span class="line">    <span class="tag"></<span class="name">beans</span>></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">beans</span> <span class="attr">profile</span>=<span class="string">"prod"</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">jee:jndi-lookup</span> <span class="attr">id</span>=<span class="string">"dataSource"</span></span></span><br><span class="line"><span class="tag">                         <span class="attr">jndi-name</span>=<span class="string">"jdbc/myDS"</span></span></span><br><span class="line"><span class="tag">                         <span class="attr">resource-ref</span>=<span class="string">"true"</span></span></span><br><span class="line"><span class="tag">                         <span class="attr">proxy-interface</span>=<span class="string">"javax.sql.DataSource"</span> /></span></span><br><span class="line">    <span class="tag"></<span class="name">beans</span>></span></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></tbody></table></figure><h3 id="激活-profile"><a href="#激活-profile" class="headerlink" title="激活 profile"></a>激活 profile</h3><p>Spring 借助两个独立的属性<code>spring.profiles.active</code>、<code>spring.profiles.default</code>来确定哪个 profile 处于激活状态，优先级从高到低。如果两个值都没有设置，那就没有激活的 profile，此时只会创建那些没有定义在 profile 中的 Bean。以下是设置这两个属性的方式</p><ul><li>作为 DispatcherServlet 的初始化参数</li><li>作为 Web 应用的上下文参数</li><li>作为 JNDI 条目</li><li>作为环境变量</li><li>作为 JVM 的系统属性</li><li>在集成测试类上，使用<code>@ActiveProfiles</code>注解设置</li></ul><p>例如在 Web 应用中，设置<code>spring.profiles.default</code>的 web.xml 文件如下</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">web-app</span> <span class="attr">xmlns</span>=<span class="string">"http://xmlns.jcp.org/xml/ns/javaee"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"</span></span></span><br><span class="line"><span class="tag">         <span class="attr">version</span>=<span class="string">"4.0"</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">context-param</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">param-name</span>></span>spring.profiles.default<span class="tag"></<span class="name">param-name</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">param-value</span>></span>dev<span class="tag"></<span class="name">param-value</span>></span></span><br><span class="line">    <span class="tag"></<span class="name">context-param</span>></span></span><br><span class="line">    </span><br><span class="line">    <span class="tag"><<span class="name">servlet</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">servlet-name</span>></span>appServlet<span class="tag"></<span class="name">servlet-name</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">servlet-class</span>></span>org.springframework.web.servlet.DispatcherServlet</span><br><span class="line">        <span class="tag"></<span class="name">servlet-class</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">init-param</span>></span></span><br><span class="line">            <span class="tag"><<span class="name">param-name</span>></span>spring.profiles.default<span class="tag"></<span class="name">param-name</span>></span></span><br><span class="line">            <span class="tag"><<span class="name">param-value</span>></span>dev<span class="tag"></<span class="name">param-value</span>></span></span><br><span class="line">        <span class="tag"></<span class="name">init-param</span>></span></span><br><span class="line">    <span class="tag"></<span class="name">servlet</span>></span></span><br><span class="line"><span class="tag"></<span class="name">web-app</span>></span></span><br></pre></td></tr></tbody></table></figure><p>该文件分别为上下文和 Servlet 设置了默认的 profile。当应用程序部署到 QA、生产或者其他环境中时，负责部署的人根据情况使用系统属性、环境变量或 JNDI 设置<code>spring.profiles.active</code>即可——其优先级比<code>spring.profiles.default</code>高</p><h2 id="条件化的-Bean"><a href="#条件化的-Bean" class="headerlink" title="条件化的 Bean"></a>条件化的 Bean</h2><p>使用 Spring 提供的注解<code>@Conditional</code>，可以实现 Bean 在符合条件的情况下才会配置的效果，不符合条件则该 Bean 会被忽略。下面的例子假设只有设置了<code>magic</code>环境属性时 Spring 才会实例化 MagicBean（事先准备），否则它将被忽略。</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">MagicConfig</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="meta">@Conditional(MagicExistsCondition::class)</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">magicBean</span><span class="params">()</span></span>: MagicBean = MagicBean()</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p><code>@Conditional</code>需要一个<code>class</code>参数，并且该类要实现<code>Condition</code>接口，重写方法<code>match</code>，如果该方法返回 true 则创建带有<code>@Conditional</code>的 Bean。</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MagicExistsCondition</span>: <span class="type">Condition {</span></span></span><br><span class="line">    </span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">matches</span><span class="params">(p0: <span class="type">ConditionContext</span>?, p1: <span class="type">AnnotatedTypeMetadata</span>?)</span></span>: <span class="built_in">Boolean</span> {</span><br><span class="line">        <span class="keyword">val</span> env = p0?.environment</span><br><span class="line">        <span class="keyword">return</span> env?.containsProperty(<span class="string">"magic"</span>) ?: <span class="literal">false</span></span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该方法传入两个参数，第一个为<code>ConditionContext</code></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">ConditionContext</span> </span>{</span><br><span class="line">    <span class="function">BeanDefinitionRegistry <span class="title">getRegistry</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">ConfigurableListableBeanFactory <span class="title">getBeanFactory</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">Environment <span class="title">getEnvironment</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">ResourceLoader <span class="title">getResourceLoader</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">ClassLoader <span class="title">getClassLoader</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>可以看到该参数为我们提供了很多接口。第二个参数为<code>AnnotatedTypeMetadata</code></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">AnnotatedTypeMetadata</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">boolean</span> <span class="title">isAnnotated</span><span class="params">(String var1)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">Map<String, Object> <span class="title">getAnnotationAttributes</span><span class="params">(String var1)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">Map<String, Object> <span class="title">getAnnotationAttributes</span><span class="params">(String var1, <span class="keyword">boolean</span> var2)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">MultiValueMap<String, Object> <span class="title">getAllAnnotationAttributes</span><span class="params">(String var1)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">MultiValueMap<String, Object> <span class="title">getAllAnnotationAttributes</span><span class="params">(String var1, <span class="keyword">boolean</span> var2)</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该参数为我们提供了一系列检查<code>@Bean</code>方法上的其他注解的接口</p><p>观察第一节提到的<code>Profile</code>注解</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Target</span>({ElementType.TYPE, ElementType.METHOD})</span><br><span class="line"><span class="meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Conditional</span>({ProfileCondition.class})</span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> Profile {</span><br><span class="line">    String[] value();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>可以看到其本身也使用了<code>@Conditional</code>，引用<code>ProfileCondition.class</code>作为 Condition 实现</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ProfileCondition</span> <span class="keyword">implements</span> <span class="title">Condition</span> </span>{</span><br><span class="line">    ProfileCondition() {</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">matches</span><span class="params">(ConditionContext context, AnnotatedTypeMetadata metadata)</span> </span>{</span><br><span class="line">        <span class="keyword">if</span> (context.getEnvironment() != <span class="keyword">null</span>) {</span><br><span class="line">            MultiValueMap<String, Object> attrs = metadata.getAllAnnotationAttributes(Profile.class.getName());</span><br><span class="line">            <span class="keyword">if</span> (attrs != <span class="keyword">null</span>) {</span><br><span class="line">                Iterator var4 = ((List)attrs.get(<span class="string">"value"</span>)).iterator();</span><br><span class="line"></span><br><span class="line">                Object value;</span><br><span class="line">                <span class="keyword">do</span> {</span><br><span class="line">                    <span class="keyword">if</span> (!var4.hasNext()) {</span><br><span class="line">                        <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">                    }</span><br><span class="line"></span><br><span class="line">                    value = var4.next();</span><br><span class="line">                } <span class="keyword">while</span>(!context.getEnvironment().acceptsProfiles((String[])((String[])value)));</span><br><span class="line"></span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该类通过 AnnotatedTypeMetadata 获得用于<code>@Profile</code>注解的所有属性，然后检查<code>value</code>属性，得到<code>profile</code>名称。然后通过 ConditionContext 检查该<code>profile</code>是否激活</p><h2 id="自动装配的歧义性"><a href="#自动装配的歧义性" class="headerlink" title="自动装配的歧义性"></a>自动装配的歧义性</h2><p>在自动装配中，只有当仅有一个 Bean 匹配所需的结果时，自动装配才是有效的，出现歧义则将阻碍 Spring 自动装配属性、构造器参数或方法参数。</p><p>比如下面的例子，提供两个 CompactDisc 的实现类</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">CompactDisc</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">play</span><span class="params">()</span></span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SgtPeppers</span>: <span class="type">CompactDisc {</span></span></span><br><span class="line">......</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BlankDisc</span>: <span class="type">CompactDisc {</span></span></span><br><span class="line">......</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>测试代码为</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner::class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = [CDPlayerConfig::class])</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CDPlayerTest</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">lateinit</span> <span class="keyword">var</span> cd: CompactDisc</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">play</span><span class="params">()</span></span> {</span><br><span class="line">    cd.play()</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">setCompactDisc</span><span class="params">(cd: <span class="type">CompactDisc</span>)</span></span> {</span><br><span class="line">        <span class="keyword">this</span>.cd = cd</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>测试将会报错</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of <span class="built_in">type</span> <span class="string">'soundsystem.CompactDisc'</span> available: expected single matching bean but found 2: blankDisc,sgtPeppers</span><br></pre></td></tr></tbody></table></figure><p>针对这种情况，可以使用<code>@Primary</code>和<code>@Qualifier</code>解决</p><h3 id="Primary"><a href="#Primary" class="headerlink" title="@Primary"></a>@Primary</h3><p>在其中一个实现上添加注解</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Primary</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SgtPeppers</span>: <span class="type">CompactDisc {</span></span></span><br><span class="line">......</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该注解也可以应用在 JavaConfig 代码中，在 XML 文件的配置方式为</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"sgtPeppers"</span> <span class="attr">class</span>=<span class="string">"sound_system.SgtPeppers"</span></span></span><br><span class="line"><span class="tag">    <span class="attr">primary</span>=<span class="string">"true"</span>></span></span><br><span class="line"><span class="tag"></<span class="name">bean</span>></span></span><br></pre></td></tr></tbody></table></figure><h3 id="Qualifier"><a href="#Qualifier" class="headerlink" title="@Qualifier"></a>@Qualifier</h3><p>该注解比<code>@Primary</code>更加灵活，使用简单</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="meta">@Qualifier(<span class="meta-string">"sgtPeppers"</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">setCompactDisc</span><span class="params">(cd: <span class="type">CompactDisc</span>)</span></span> {</span><br><span class="line"><span class="keyword">this</span>.cd = cd</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>为<code>@Qualifier</code>注解设置的参数就是想要注入的 Bean 的 id，一般为 Bean 的类名首字母变为小写之后的字符。当然开发者也可以自定义 Bean 的限定符防止重命名之后原来的注解失效</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Qualifier(<span class="meta-string">"byBeatles"</span>)</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SgtPeppers</span>: <span class="type">CompactDisc {</span></span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> title = <span class="string">"Sgt. Pepper's Lonely Hearts Club Band"</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> artist = <span class="string">"The Beatles"</span></span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">play</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Playing <span class="variable">$title</span> by <span class="variable">$artist</span>"</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>相应的代码也更改为</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="meta">@Qualifier(<span class="meta-string">"byBeatles"</span>)</span></span><br><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">setCompactDisc</span><span class="params">(cd: <span class="type">CompactDisc</span>)</span></span> {</span><br><span class="line"><span class="keyword">this</span>.cd = cd</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>更进一步，开发者可以自定义一个使用<code>@Qualifier</code>注解的注解类，防止以后再出现一个<code>byBeatles</code>的 Bean</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> org.springframework.beans.factory.<span class="keyword">annotation</span>.Qualifier</span><br><span class="line"></span><br><span class="line"><span class="meta">@Target(AnnotationTarget.CONSTRUCTOR, AnnotationTarget.FIELD, AnnotationTarget.TYPE, AnnotationTarget.FUNCTION)</span></span><br><span class="line"><span class="meta">@Retention(AnnotationRetention.RUNTIME)</span></span><br><span class="line"><span class="meta">@Qualifier</span></span><br><span class="line"><span class="keyword">annotation</span> <span class="class"><span class="keyword">class</span> <span class="title">FavoriteCD</span></span></span><br></pre></td></tr></tbody></table></figure><p>使用方法和<code>@Qualifier("id")</code>类似，在需要注解的类上添加<code>@FavoriteCD</code>，然后在自动装配的地方同样使用该注解即可。</p><h2 id="Bean-的作用域"><a href="#Bean-的作用域" class="headerlink" title="Bean 的作用域"></a>Bean 的作用域</h2><p> 在上一篇文章中我们提到 Spring 中的 Bean 默认是单例的，但是显然这样的实例将会保持一定的“状态”，因此重用将是不安全的。Spring 定义了多种 Bean 作用域</p><ul><li>单例（Singleton）：整个应用中只创建 Bean 的一个实例</li><li>原型（Prototype）：每次注入或者通过 Spring 应用上下文获取的时候都会创建一个新的实例</li><li>会话（Session）：在 Web 应用中，为每个会话创建一个实例</li><li>请求（Request）：在 Web 应用中，为每个请求创建一个实例</li></ul><p>要使用作用域，可以通过<code>@Scope</code>注解实现</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SgtPeppers</span></span></span><br></pre></td></tr></tbody></table></figure><p>当然在自动装配的地方使用该注解也可以，XML 文件的配置方式同理，不再赘述</p><h3 id="会话和请求作用域"><a href="#会话和请求作用域" class="headerlink" title="会话和请求作用域"></a>会话和请求作用域</h3><p>在典型的电子商务应用中，可能有一个 Bean 代表用户的购物车，当其为单例时意味着所有的用户共用一个购物车。这种情况下使用会话作用域显然更加合适</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="meta">@Scope(value = WebApplicationContext.SCOPE_SESSION,</span></span><br><span class="line"><span class="meta">        proxyMode = ScopedProxyMode..TARGET_CLASS)</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">ShoppingCart</span></span></span><br></pre></td></tr></tbody></table></figure><p>注解的第一个参数<code>value</code>将告诉 Spring 为 Web 应用的每个会话创建一个 ShoppingCart，在会话相关的操作中其相当于一个单例。第二个参数<code>proxyMode</code>被设为<code>ScopedProxyMode.INTERFACES</code>，解决将会话或者请求作用域的 Bean 注入到单例 Bean 中所遇到的问题</p><p>假设 ShoppingCart 注入到一个单例 StoreService 中，由于单例 Bean 会在 Spring 应用上下文加载的时候创建，然而此时属于会话作用域的 ShoppingCart 并不存在；此外，系统中将会有多个 ShoppingCart 实例，我们并不想让某一个固定的 ShoppingCart 实例到 StoreService 中，而是希望当 StoreService 处理购物车功能时，其所使用的 ShoppingCart 实例刚好是当前会话所对应的那个</p><p><code>proxyMode</code>的作用就在于，Spring 不会将实际的 ShoppingCart 注入到 StoreService 中，而是将它的代理注入。这一代理会暴露与 ShoppingCart 相同的方法，所以 StoreService 会认为它就是一个购物车。当 StoreService 调用方法时，代理会进行懒解析并将调用委托到会话作用域内真正的 ShoppingCart 实例</p><p>XML 方式配置不再赘述</p><h2 id="Spring-表达式语言"><a href="#Spring-表达式语言" class="headerlink" title="Spring 表达式语言"></a>Spring 表达式语言</h2><p>Spring Expression Language，简称 SpEL，能够以一种强大和简洁的方式将值装配到 Bean 属性和构造器参数中。其有很多特性</p><ul><li>使用 ID 来引用 Bean</li><li>调用方法和访问对象的属性</li><li>对值进行算数、关系和逻辑运算</li><li>正则表达式匹配</li><li>集合操作</li></ul><p>SpEL 要放到<code>#{ ... }</code>中，与属性占位符放到<code>${ ... }</code>中类似。对于下面的表达式</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line">#{T(System).currentTimeMillis()}</span><br></pre></td></tr></tbody></table></figure><p>将得到表达式计算的那一刻时间的毫秒数。<code>T()</code>表达式将其中参数视为 Java 中的类型，从而可以调用该类型的静态方法。</p><p><code>#{ ... }</code>中既可以放字面值如整数、浮点数、字符串、布尔值，也可以引用对象属性和方法</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line">#{sgtPeppers.artist}</span><br><span class="line"></span><br><span class="line">#{systemProperties[<span class="string">'sgtPeppers.title'</span>]}</span><br><span class="line"></span><br><span class="line">#{sgtPeppers.toString()?.toUpperCase}</span><br></pre></td></tr></tbody></table></figure><p>上面的第二个表达式将调用<code>properties</code>文件中<code>sgtPeppers</code>的<code>title</code>属性值。第三个参数表示我们可以对方法返回值使用安全调用</p><p>SpEL 支持各种常用的运算，包括算术运算、比较运算、逻辑运算、条件运算（<code>? :</code>和<code>?:</code>）、正则表达式（<code>matches</code>），下面是一个正则表达式的例子，用以验证邮箱</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line">#{admin.email matches <span class="string">'[a-zA-Z0-9._&+-]+@[a-zA-Z0-9._&+-]'</span>+\\.com}</span><br></pre></td></tr></tbody></table></figure><p>下面是一个操作集合的例子</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line">#{cd.tracks[<span class="number">1</span>].title}</span><br><span class="line"></span><br><span class="line">#{<span class="string">'the string'</span>[<span class="number">1</span>]} <span class="comment">//得到 h</span></span><br><span class="line"></span><br><span class="line">#{cd.tracks.?[title eq <span class="string">'Title'</span>} <span class="comment">//得到 title 为 Title 的新集合</span></span><br><span class="line"></span><br><span class="line">#{cd.tracks.^[title eq <span class="string">'Title'</span>} <span class="comment">//得到第一个 title 值为 Title 的元素</span></span><br><span class="line">              </span><br><span class="line">#{cd.tracks.$[title eq <span class="string">'Title'</span>} <span class="comment">//得到最后一个 title 值为 Title 的元素             </span></span><br><span class="line"></span><br><span class="line">#{cd.tracks.![title]} <span class="comment">//得到 title 的集合</span></span><br></pre></td></tr></tbody></table></figure><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;开发软件的过程中要涉及到不同的环境，需要配置不同的数据库配置、加密算法和外部环境的集成等组件，为此必须要考虑不同的环境下对应不同的配置。本文将从 Spring profile 出发，进而介绍条件化的 Bean 声明、自动装配的歧义性和 Bean 的作用域，以及 Spring 表达式语言。&lt;/p&gt;
    
    </summary>
    
      <category term="Spring" scheme="http://yoursite.com/categories/Spring/"/>
    
    
      <category term="Spring" scheme="http://yoursite.com/tags/Spring/"/>
    
  </entry>
  
  <entry>
    <title>Spring-学习笔记（一）：基本理念和-Bean-装配</title>
    <link href="http://yoursite.com/Spring-%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0%EF%BC%88%E4%B8%80%EF%BC%89%EF%BC%9A%E5%9F%BA%E6%9C%AC%E7%90%86%E5%BF%B5%E5%92%8C-Bean-%E8%A3%85%E9%85%8D/"/>
    <id>http://yoursite.com/Spring-学习笔记（一）：基本理念和-Bean-装配/</id>
    <published>2019-05-28T14:13:09.000Z</published>
    <updated>2019-06-05T07:21:28.000Z</updated>
    
    <content type="html"><![CDATA[<p>很早就接触 Java 后端开发，不过大都浅尝辄止，并没有深刻地理解和实践，时间一长倒也跟没学过差不了多少。由于职业规划是要从 Android 开发慢慢转到后端开发，拖到现在终于是要系统学习了。学习的路线初步定为，从 Spring 出发，不断回顾 Java Web 的知识，重点在 Spring Boot 项目，通过具体的项目实践掌握相关知识。这篇文章属于第一篇学习笔记，参考手上的《Spring 实战》和网络上的技术文章，主要记录 Spring 的基本知识和理念、Bean  的装配。</p><p><img src="https://github.com/spring-projects/spring-framework/raw/master/src/docs/asciidoc/images/spring-framework.png" alt></p><a id="more"></a><h2 id="关于"><a href="#关于" class="headerlink" title="关于"></a>关于</h2><h3 id="Spring"><a href="#Spring" class="headerlink" title="Spring"></a>Spring</h3><p>一个 Java EE 框架，同来替代更加重量级的企业级 Java 技术。其起源可以追溯到作者 Rod Johnson 2002年编写的《Expert One-to-One J2EE Design and Development》一书，书中他提出了一种基于普通 Java 类和依赖注入的解决方案。Rod Johnson 编写了超过30,000行基础结构代码，这便是最初的 Spring 框架。</p><p>Spring 的基本理念在于：简化 Java 开发，为此采用了4种关键措施：</p><ul><li>基于 POJO 的轻量级和最小侵入性编程</li><li>通过依赖注入和面向接口实现松耦合</li><li>基于切面和惯例进行声明式编程</li><li>通过切面和模板减少样板式代码</li></ul><h3 id="依赖注入"><a href="#依赖注入" class="headerlink" title="依赖注入"></a>依赖注入</h3><p>依赖注入（Dependency Injection，简称 DI）是最常见的控制反转（Inversion of Control，简称 IoC）方式，是面向对象编程中的一种设计原则，用于减少代码之间的耦合度。系统通过引入实现 IoC 模式的容器，管理对象的声明周期、依赖关系，常用的 IoC 容器有 Spring 、JBoss、EJB 等。可以把 IoC 模式看作工厂模式的升华，区别在于该工厂要生成的对象是通过 XML 文件、配置类或者注解来配置。</p><p>Java 项目大都是由众多类组成，这些类相互协作来完成特定的业务逻辑。传统的做法是每个对象负责管理与自己相互协作的对象（即它所依赖的对象）的引用，导致代码高度耦合。耦合具有两面性，一方面，紧密耦合的代码难以测试、难以复用、难以理解，并且修复 Bug 的过程中容易引发更多的 Bug；另一方面，一定的耦合又是必须的——完全无耦合的代码什么也做不了。通过 DI，对象的依赖关系交由系统中负责协调各对象的第三者在创建对象时设定，依赖关系被注入到需要它们的对象中。</p><p>通过一个简单的 Spring Demo 引入依赖注入的思想。</p><p>项目结构如下</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">├─src</span><br><span class="line">│  ├─main</span><br><span class="line">│  │  ├─java</span><br><span class="line">│  │  │  └─knigt</span><br><span class="line">│  │  │          BraveKnight.kt</span><br><span class="line">│  │  │          SlayDragonQuest.kt</span><br><span class="line">│  │  │          KnightConfig.kt</span><br><span class="line">│  │  │          KnightMain.kt</span><br><span class="line">│  │  │</span><br><span class="line">│  │  └─resources</span><br><span class="line">│  │          knights.xml</span><br></pre></td></tr></tbody></table></figure><blockquote><p>在 IDEA 内通过以下命令生成项目文档树</p><p>tree  >>    D:/tree.txt 输出文件夹<br>tree /f >>    D:/tree.txt 输出文件夹和文件</p></blockquote><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//BraveKnight.kt</span></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">Knight</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">embarkOnQuest</span><span class="params">()</span></span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BraveKnight</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> quest: Quest): Knight {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">embarkOnQuest</span><span class="params">()</span></span> {</span><br><span class="line">        quest.embark()</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//SlayDragonQuest.kt</span></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">Quest</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">embark</span><span class="params">()</span></span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SlayDragonQuest</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> stream: PrintStream): Quest {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">embark</span><span class="params">()</span></span> {</span><br><span class="line">        stream.println(<span class="string">"Embarking on quest to slay the dragon!"</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//KnightConfig.kt</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">KnightConfig</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">knight</span><span class="params">()</span></span>: Knight = BraveKnight(quest())</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">quest</span><span class="params">()</span></span>: Quest = SlayDragonQuest(System.<span class="keyword">out</span>)</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//KnightMain.kt</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">KnightMain</span> </span>{</span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> {</span><br><span class="line">        <span class="meta">@JvmStatic</span></span><br><span class="line">        <span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span><<span class="type">String</span>>)</span></span> {</span><br><span class="line">            <span class="keyword">val</span> context: AnnotationConfigApplicationContext = AnnotationConfigApplicationContext(KnightConfig::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span></span><br><span class="line">            <span class="comment">//val context: ClassPathXmlApplicationContext = ClassPathXmlApplicationContext("knights.xml")   XML 方式配置</span></span><br><span class="line">            <span class="keyword">val</span> knight: Knight = context.getBean(Knight::<span class="class"><span class="keyword">class</span>.<span class="title">java</span>)</span></span><br><span class="line">            knight.embarkOnQuest()</span><br><span class="line">            context.close()</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>XML 的配置文件为</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line">//knight.xml</span><br><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">  <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">  <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans </span></span></span><br><span class="line"><span class="tag"><span class="string">      http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></span><br><span class="line"></span><br><span class="line">  <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"knight"</span> <span class="attr">class</span>=<span class="string">"sia.knights.BraveKnight"</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">ref</span>=<span class="string">"quest"</span> /></span></span><br><span class="line">  <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line">  <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"quest"</span> <span class="attr">class</span>=<span class="string">"sia.knights.SlayDragonQuest"</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"#{T(System).out}"</span> /></span></span><br><span class="line">  <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></tbody></table></figure><p>输出结果为</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Embarking on quest to slay the dragon!</span><br></pre></td></tr></tbody></table></figure><h3 id="面向切面编程"><a href="#面向切面编程" class="headerlink" title="面向切面编程"></a>面向切面编程</h3><p>Aspect-Oriented Programming，AOP。DI 能够让相互协作的组件保持松耦合，而 AOP 则允许开发者把组件除自身核心功能之外，可重用的功能分离出来，比如日志、事务管理和安全等服务。AOP 思想中的相关术语包括</p><ul><li>Joinpoint：连接点，目标对象中所有可增强的方法</li><li>Pointcut：切入点，目标对象中所有已增强的方法</li><li>Advice：增强/通知，增强的代码</li><li>Weaving：织入，将“通知”应用到切入点的过程</li><li>Proxy：将“通知”织入到目标对象后，形成代理对象</li><li>Aspect：切面，切入点+“通知”</li></ul><p>使用一个传唱骑士事迹的吟游诗人服务类作为一个 AOP 的应用</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//Minstrel.kt</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">Minstrel</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> stream: PrintStream) {</span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">singBeforeQuest</span><span class="params">()</span></span> {</span><br><span class="line">        stream.println(<span class="string">"Fa la la, the knight is so brave!"</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">singAfterQuest</span><span class="params">()</span></span> {</span><br><span class="line">        stream.println(<span class="string">"Tee hee hee, the brave knight did embark on a quest!"</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><figure class="highlight html"><table><tbody><tr><td class="code"><pre><span class="line">//knights.xml 加入以下代码</span><br><span class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"minstrel"</span> <span class="attr">class</span>=<span class="string">"Minstrel"</span>></span></span><br><span class="line">    <span class="tag"><<span class="name">constructor-arg</span> <span class="attr">value</span>=<span class="string">"#{T(System).out}"</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">aop:config</span>></span></span><br><span class="line"><span class="tag"><<span class="name">aop:aspect</span> <span class="attr">ref</span>=<span class="string">"minstrel"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">aop:pointcut</span> <span class="attr">id</span>=<span class="string">"embark"</span> <span class="attr">expression</span>=<span class="string">"execution(* *.embarkOnQuest(..))"</span> /></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">aop:before</span> <span class="attr">pointcut-ref</span>=<span class="string">"embark"</span> <span class="attr">method</span>=<span class="string">"singBeforeQuest"</span> /></span></span><br><span class="line"></span><br><span class="line"><span class="tag"><<span class="name">aop:after</span> <span class="attr">pointcut-ref</span>=<span class="string">"embark"</span> <span class="attr">method</span>=<span class="string">"singAfterQuest"</span> /></span></span><br><span class="line"><span class="tag"></<span class="name">aop:aspect</span>></span></span><br><span class="line"><span class="tag"></<span class="name">aop:config</span>></span></span><br></pre></td></tr></tbody></table></figure><p><em>默认缺少 org.aspectj.aspectjweaver，可以引入 maven，添加对应的依赖</em></p><p>运行之后，控制台输出结果为</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Fa la la, the knight is so brave!</span><br><span class="line">Embarking on quest to slay the dragon!</span><br><span class="line">Tee hee hee, the brave knight did embark on a quest!</span><br></pre></td></tr></tbody></table></figure><p>不使用 BraveKnight 和 Minstrel 组合的方式而是使用 AOP，通过少量的 XML 配置，就可以把 Minstrel 声明为一个 Spring 切面，进而实现相应的功能。Minstrel 仍然只是一个 POJO，但可以被应用到任何需要它的地方，只需要修改配置文件中的 AspectJ 切点表达式语言即可。</p><h2 id="装配-Bean"><a href="#装配-Bean" class="headerlink" title="装配 Bean"></a>装配 Bean</h2><h3 id="容器"><a href="#容器" class="headerlink" title="容器"></a>容器</h3><p>Spring 容器（Container）负责创建、装配和配置对象，并且管理它们的生命周期。Spring 的核心便是容器，它自带多个容器实现，可以归为两种不同的类型：Bean 工厂（由<code>org.springframework.beans.factory.BeanFactory</code>接口定义）是最简单的容器，提供基本的 DI 支持；应用上下文（由<code>org.springframework.context.ApplicationContext</code>接口定义），基于 BeanFactory 构建，并提供应用框架级别的服务，例如从属性文本解析文本信息以及发送应用事件给感兴趣的事件监听者。重点在于应用上下文上。</p><p>Spring 自带多种应用上下文</p><ul><li>AnnotationConfigApplicationContext：从一个或多个基于 Java 的配置类中加载 Spring 应用上下文</li><li>AnnotationConfigWebApplicationContext：从一个或多个基于 Java 的配置类中加载 Spring Web 应用上下文</li><li>ClassPathXmlApplicationContext：从类路径下的一个或多个 XML 配置文件中加载上下文定义，把应用上下文的定义文件作为类资源</li><li>FileSystemXmlApplicationContext：从文件系统的一个或多个 XML 配置文件中加载上下文定义</li><li>XmlWebApplicationContext：从 Web 应用下的一个或多个 XML 配置文件中加载上下文定义</li></ul><h3 id="Bean-生命周期"><a href="#Bean-生命周期" class="headerlink" title="Bean 生命周期"></a>Bean 生命周期</h3><p><img src="/Spring-学习笔记（一）：基本理念和-Bean-装配/生命周期.png" alt></p><ol><li>Spring 对 bean 进行实例化</li><li>Spring 将值和 bean 的引用注入到 bean 对应的属性中</li><li>如果 bean 实现了 BeanNameAware 接口，Spring 将 bean 的 ID 传递给<code>setBeanName</code>方法</li><li>如果 bean 实现了 BeanFactoryAware 接口，Spring 将调用<code>setBeanFactory</code>方法，将 BeanFactory 容器实例传入</li><li>如果 bean 实现了 ApplicationContextAware 接口， Spring 将调用<code>setApplicationContext</code>方法，将 bean 所在的应用上下文的引用传入</li><li>如果 bean 实现了 BeanPostProcessor 接口，Spring 将调用它们的<code>postProcessBeforeInitialization</code>方法</li><li>如果 bean 实现了 InitializingBean 接口，Spring 将调用它们的 <code>afterPropertiesSet</code>方法。类似的，如果 bean 使用 init-method 声明了初始化方法，该方法也会被调用</li><li>如果 bean 实现了 BeanPostProcessor 接口，Spring 将调用它们的<code>postProcessAfterInitialzation</code>方法</li><li>此时，bean 已经准备就绪，可以被应用程序使用了，它们将一直驻留在应用上下文中，直到该应用上下文被销毁</li><li>如果 bean 实现了 DisposablBean 接口，Spring 将调用它的<code>destroy</code>接口方法。同样，如果 bean 使用 destroy-method 声明了销毁方法，该方法也会被调用</li></ol><h3 id="可选装配方案"><a href="#可选装配方案" class="headerlink" title="可选装配方案"></a>可选装配方案</h3><p>有三种方式向 Spring 容器描述 Bean 如何装配</p><ul><li>在 XML 文件中进行显示配置</li><li>在 Java 代码中进行显示配置</li><li>隐式的 bean 发现机制和自动装配</li></ul><h4 id="自动化装配-bean"><a href="#自动化装配-bean" class="headerlink" title="自动化装配 bean"></a>自动化装配 bean</h4><p>Spring 从两个角度实现自动化装配</p><ul><li>组件扫描（Component Scanning）：Spring 会自动发现应用上下文中所创建的 bean</li><li>自动装配（Autowiring）：Spring 自动满足 bean 之间的依赖</li></ul><p>通过一个 CD 播放的 Demo 说明，XML config 文件位于<code>/resources</code>目录下，Kotlin 文件位于<code>java/soundsystem</code>目录下</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//conpactDisc.kt</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CompactDisc</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> title = <span class="string">"Sgt. Pepper's Lonely Hearts Club Band"</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">val</span> artist = <span class="string">"The Beatles"</span></span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">play</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Playing <span class="variable">$title</span> by <span class="variable">$artist</span>"</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//CDPlayer.kt</span></span><br><span class="line"><span class="meta">@Component</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CDPlayer</span> <span class="meta">@Autowired</span></span></span><br><span class="line"><span class="keyword">constructor</span>(<span class="keyword">private</span> <span class="keyword">val</span> cd: CompactDisc) {</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">play</span><span class="params">()</span></span> {</span><br><span class="line">        cd.play()</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//CDPlayerTest.kt</span></span><br><span class="line"><span class="meta">@RunWith(SpringJUnit4ClassRunner::class)</span></span><br><span class="line"><span class="meta">@ContextConfiguration(classes = [CDPlayerConfig::class])</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">CDPlayerTest</span> </span>{</span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">lateinit</span> <span class="keyword">var</span> player: CDPlayer</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Test</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">play</span><span class="params">()</span></span> {</span><br><span class="line">        player.play()</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>以上便是准备好的工程文件，使用<code>@Autowired</code>注解在需要自动装配的任何属性、构造器、方法上，然后在 Java 代码或者 XML 配置文件启动自动装配。不同的自动装配方式需要修改 JUnit 测试框架的<code>@ContextConfiguration</code>注解参数</p><ul><li><p>XML 方式：<code>locations = ["classpath:spring-config.xml"]</code></p></li><li><p>Java 方式：<code>classes = [CDPlayerConfig::class]</code></p></li></ul><p><em>JUnit 测试框架需要导入依赖</em></p><p>接下来的一步便是启用 Spring 的组件扫描，也就是配置以上的两种注解参数之一，搭配 Bean 类注解<code>@Component</code>便可以实现组件扫描。以下两种做法都可以，第一种是使用一个<code>Config</code>类并添加注解<code>@Configuration</code>和<code>@ConponentScan</code>，第二种则是在 XML 文件中配置<code>context:component-scan</code></p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//Java 代码</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ComponentScan</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">CDPlayerConfig</span></span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line">//XML 文件</span><br><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:context</span>=<span class="string">"http://www.springframework.org/schema/context"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"</span>></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">context:component-scan</span> <span class="attr">base-package</span>=<span class="string">"soundsystem"</span> /></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></tbody></table></figure><p>运行测试，控制台成功输出</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Playing Sgt. Pepper<span class="string">'s Lonely Hearts Club Band by The Beatles</span></span><br></pre></td></tr></tbody></table></figure><h4 id="Java-代码装配"><a href="#Java-代码装配" class="headerlink" title="Java 代码装配"></a>Java 代码装配</h4><p>大多数情况下都可以通过自动化装配的方式完成配置，但是当我们使用第三方的类库组件时，无法在组件类上直接添加注解，只能使用显示装配。这种情况下，使用 JavaConfig 是最好的方式。通常将 JavaConfig 放到同一个包中，并且它应该不包含任何的业务逻辑。</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">CDPlayerConfig</span> </span>{</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">sgtPeppers</span><span class="params">()</span></span>: SgtPeppers = SgtPeppers()</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">cdPlayer</span><span class="params">()</span></span>: CDPlayer = CDPlayer(sgtPeppers())</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">anotherCDPlayer</span><span class="params">()</span></span>: CDPlayer = CDPlayer(sgtPeppers())</span><br><span class="line">    </span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="keyword">open</span> <span class="function"><span class="keyword">fun</span> <span class="title">cdPlayerUseParam</span><span class="params">(compactDisc: <span class="type">CompactDisc</span>)</span></span>: CDPlayer {</span><br><span class="line">        <span class="keyword">val</span> cdPlayer = CDPlayer(compactDisc)</span><br><span class="line">        cdPlayer.setCompactDisc(compactDisc)</span><br><span class="line">        <span class="keyword">return</span> cdPlayer</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在上面的代码中，配置类<code>CDPlayerConfig</code>中提供了几种装配 Bean  的方式。<code>sgtPeppers</code>方法注入了一个 SgtPeppers 实例，<code>cdPlayer</code>和<code>anotherCDPlayer</code>需要调用该方法来注入 CDPlayer 实例。Spring 会拦截所有对该方法的调用，并直接返回该方法创建的 Bean，并且默认情况下 Bean 是单例的。</p><p>最后一个方法则展现了一个更具可读性、更多样化的注入方式。</p><h4 id="XML-代码装配"><a href="#XML-代码装配" class="headerlink" title="XML 代码装配"></a>XML 代码装配</h4><p>最简单的 Spring XML 配置如下</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></span><br><span class="line">    </span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></tbody></table></figure><p>需要在配置文件顶部声明多个 XML 模式（XSD）文件，这些文件定义了配置 Spring 的 XML 元素。<code><beans></code>配置文件的根元素。</p><p>声明一个简单的 Bean 可以通过下面的方式实现</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"cdPlayer"</span> <span class="attr">class</span>=<span class="string">"sound_system.CDPlayer"</span>></span></span><br><span class="line"><span class="tag"><<span class="name">constructor-arg</span> <span class="attr">ref</span>=<span class="string">"sgtPeppers"</span>/></span></span><br><span class="line"><span class="tag"></<span class="name">bean</span>></span></span><br><span class="line">    </span><br><span class="line"><span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"sgtPeppers"</span> <span class="attr">class</span>=<span class="string">"sound_system.SgtPeppers"</span> /></span></span><br></pre></td></tr></tbody></table></figure><p>如果没有指定 id，该 Bean 将会根据全限定类名命名：<code>sound_system.CDPlayer#0</code>，<code>#0</code>为计数形式，用以区分相同类型的其他 Bean。</p><p><code>&lt;constructor-arg&gt;</code>元素告知 Spring 要将一个 id 为<code>sgtPeppers</code>的 bean 引用传递到 CDPlayer 的构造器中。作为替代，也可以使用 Spring 的 c-命名空间，不再赘述。关于<code>&lt;constructor-arg&gt;</code>元素的更多知识：<a href="http://cmsblogs.com/?p=2754" target="_blank" rel="noopener">IOC 之解析 bean 标签：constructor-arg、property 子元素</a></p><p>在该元素中，可以使用<code>value</code>属性将字面量传递给构造器，使用子元素<code>&lt;null/&gt;</code>空值，使用子元素<code>&lt;list&gt;</code>传递集合。</p><p>XML 配置也提供了设置属性的接口，通过元素<code>property</code>可以将对象引用、字面量、集合等注入 Bean 中。比如为 SgtPeppers 添加新的属性并对外提供修改接口</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SgtPeppers</span>: <span class="type">CompactDisc {</span></span></span><br><span class="line">    <span class="keyword">var</span> title = <span class="string">"Sgt. Pepper's Lonely Hearts Club Band"</span></span><br><span class="line">    <span class="keyword">var</span> artist = <span class="string">"The Beatles"</span></span><br><span class="line">    <span class="keyword">var</span> tracks: MutableList<String> = ArrayList()</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">play</span><span class="params">()</span></span> {</span><br><span class="line">        println(<span class="string">"Playing <span class="variable">$title</span> by <span class="variable">$artist</span>"</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>然后在 XML 文件中注入属性设置</p><figure class="highlight xml"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta"><?xml version="1.0" encoding="UTF-8"?></span></span><br><span class="line"><span class="tag"><<span class="name">beans</span> <span class="attr">xmlns</span>=<span class="string">"http://www.springframework.org/schema/beans"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xmlns:xsi</span>=<span class="string">"http://www.w3.org/2001/XMLSchema-instance"</span></span></span><br><span class="line"><span class="tag">       <span class="attr">xsi:schemaLocation</span>=<span class="string">"http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"</span>></span></span><br><span class="line"></span><br><span class="line">    <span class="tag"><<span class="name">bean</span> <span class="attr">id</span>=<span class="string">"sgtPeppers"</span> <span class="attr">class</span>=<span class="string">"sound_system.SgtPeppers"</span>></span></span><br><span class="line">        <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"title"</span> <span class="attr">value</span>=<span class="string">"new title"</span> /></span></span><br><span class="line">        <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"artist"</span> <span class="attr">value</span>=<span class="string">"new artist"</span> /></span></span><br><span class="line">        <span class="tag"><<span class="name">property</span> <span class="attr">name</span>=<span class="string">"tracks"</span>></span></span><br><span class="line">            <span class="tag"><<span class="name">list</span>></span></span><br><span class="line">                <span class="tag"><<span class="name">value</span>></span>track 1<span class="tag"></<span class="name">value</span>></span></span><br><span class="line">                <span class="tag"><<span class="name">value</span>></span>track 2<span class="tag"></<span class="name">value</span>></span></span><br><span class="line">                <span class="tag"><<span class="name">value</span>></span>track 3<span class="tag"></<span class="name">value</span>></span></span><br><span class="line">                <span class="tag"><<span class="name">value</span>></span>track 4<span class="tag"></<span class="name">value</span>></span></span><br><span class="line">            <span class="tag"></<span class="name">list</span>></span></span><br><span class="line">        <span class="tag"></<span class="name">property</span>></span></span><br><span class="line">    <span class="tag"></<span class="name">bean</span>></span></span><br><span class="line"></span><br><span class="line"><span class="tag"></<span class="name">beans</span>></span></span><br></pre></td></tr></tbody></table></figure><h3 id="导入和混合配置"><a href="#导入和混合配置" class="headerlink" title="导入和混合配置"></a>导入和混合配置</h3><p>当开发者使用自动装配的时候，Spring 会考虑容器中的所有 Bean，因此不用考虑混合配置的问题。混合配置应用在显式装配时，比如 XML 和 Java 互相引用配置的 Bean。</p><p>在 JavaConfig 中使用注解<code>@Import</code>导入其他 JavaConfig，使用注解<code>@ImportResource</code>混合 XML 配置，比如</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@Import(CDPlayerConfig::class)</span></span><br><span class="line"><span class="meta">@ImportResource(<span class="meta-string">"classpath:sound_system.xml"</span>)</span></span><br><span class="line"><span class="keyword">open</span> <span class="class"><span class="keyword">class</span> <span class="title">SoundSystemConfig</span></span></span><br></pre></td></tr></tbody></table></figure><p>在 XML 中使用元素<code>import</code>导入其他 XML 文件，而如果要导入 JavaConfig，做法很简单 ：将其声明为一个 bean 即可。</p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;很早就接触 Java 后端开发，不过大都浅尝辄止，并没有深刻地理解和实践，时间一长倒也跟没学过差不了多少。由于职业规划是要从 Android 开发慢慢转到后端开发，拖到现在终于是要系统学习了。学习的路线初步定为，从 Spring 出发，不断回顾 Java Web 的知识，重点在 Spring Boot 项目，通过具体的项目实践掌握相关知识。这篇文章属于第一篇学习笔记，参考手上的《Spring 实战》和网络上的技术文章，主要记录 Spring 的基本知识和理念、Bean  的装配。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://github.com/spring-projects/spring-framework/raw/master/src/docs/asciidoc/images/spring-framework.png&quot; alt&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="Spring" scheme="http://yoursite.com/categories/Spring/"/>
    
    
      <category term="Spring" scheme="http://yoursite.com/tags/Spring/"/>
    
      <category term="AOP" scheme="http://yoursite.com/tags/AOP/"/>
    
      <category term="DI" scheme="http://yoursite.com/tags/DI/"/>
    
      <category term="Java EE" scheme="http://yoursite.com/tags/Java-EE/"/>
    
  </entry>
  
  <entry>
    <title>Gradle 构建工具详解</title>
    <link href="http://yoursite.com/Gradle-%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7%E8%AF%A6%E8%A7%A3/"/>
    <id>http://yoursite.com/Gradle-构建工具详解/</id>
    <published>2019-05-25T14:37:41.000Z</published>
    <updated>2019-06-02T05:00:46.000Z</updated>
    
    <content type="html"><![CDATA[<p>Gradle 是一个基于 JVM 的自动化构建工具，使用 Groovy  DSL 声明配置。现代软件开发包含众多步骤，包括编译、测试、打包等，如果需要手动重复每一过程，将会耗费大量时间、增加出错概率，项目自动化应运而生。在此之前，常见的 Java 构建工具包括 Ant、Gant 和 Maven 等，Gradle 结合了以上工具的优点，基于约定大于配置，通用灵活，是 Android 的官方构建工具。本文将介绍 Gradle 的基本知识、Groovy 基本语法以及 Android 开发中的 Gradle 知识。</p><p><img src="https://plugins.gradle.org/shared-assets/shared/images/gradle-logo-horizontal.svg" alt></p><a id="more"></a><h2 id="入门"><a href="#入门" class="headerlink" title="入门"></a>入门</h2><h3 id="搭建环境"><a href="#搭建环境" class="headerlink" title="搭建环境"></a>搭建环境</h3><p>确保系统已安装 JDK 1.7及以上，此处将介绍 Gradle 在 Windows 平台下的手动安装。在 <a href="https://gradle.org/releases/" target="_blank" rel="noopener">https://gradle.org/releases/</a> 下载最新的 release 包并解压至相应文件夹，然后在系统环境变量添加<code>GRADLE_HOME</code>，作者的变量值为<code>D:\gradle-5.4.1</code>，最后再将<code>%GRADLE_HOME%\bin</code>添加进<code>Path</code>变量中即可。在命令行中键入<code>gradle -v</code>验证环境搭建结果。</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">PS C:\Users\Febers> gradle -v</span><br><span class="line"></span><br><span class="line">Welcome to Gradle 5.4.1!</span><br><span class="line"></span><br><span class="line">Here are the highlights of this release:</span><br><span class="line"> - Run builds with JDK12</span><br><span class="line"> - New API <span class="keyword">for</span> Incremental Tasks</span><br><span class="line"> - Updates to native projects, including Swift 5 support</span><br><span class="line"></span><br><span class="line">For more details see https://docs.gradle.org/5.4.1/release-notes.html</span><br></pre></td></tr></tbody></table></figure><h3 id="Hello-World"><a href="#Hello-World" class="headerlink" title="Hello World"></a>Hello World</h3><p>新建一个工程项目，比如<code>gradle_demo</code>，然后新建一个<code>build.gradle</code>文件，在其中输入</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">task hello {</span><br><span class="line">  doLast {</span><br><span class="line">    println <span class="string">'Hello World!'</span></span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>在当前文件夹命令行中键入<code>gradle -q hello</code>即可输出<code>Hello  World!</code>。这里使用的是基于 Groovy 的 DSL（Domain Specific Language，领域特定语言）。</p><p>task 和 action 是 Gradle 的重要元素，前者代表一个独立的原子操作，比如复制一个文件、编译一次 Java 代码，这里简单定义一个名为<code>hello</code>的 task；后者则是前者的组成部分，<code>doLast</code>代表 task 执行的最后一个 action，task 执行完毕之后会回调该 action。</p><h3 id="日志级别"><a href="#日志级别" class="headerlink" title="日志级别"></a>日志级别</h3><p>和 Android 类似，Gradle 也定义了日志级别</p><table><thead><tr><th>级别</th><th>用于</th></tr></thead><tbody><tr><td>ERROR</td><td>错误信息</td></tr><tr><td>QUIET</td><td>重要信息</td></tr><tr><td>WARNING</td><td>警告信息</td></tr><tr><td>LIFECYCLE</td><td>进度信息</td></tr><tr><td>INFO</td><td>信息消息</td></tr><tr><td>DEBUG</td><td>调试消息</td></tr></tbody></table><p>上文运行任务所使用到的命令<code>gradle -q hello</code>中的<code>-q</code>即为日志的级别开关选项</p><table><thead><tr><th>开关选项</th><th>输出级别</th></tr></thead><tbody><tr><td>无</td><td>LIFECYCLE及以上</td></tr><tr><td>-q 或 –quiet</td><td>QUIET及以上</td></tr><tr><td>-i 或 –info</td><td>INFO及以上</td></tr><tr><td>-d 或 –debug</td><td>DEBUG及以上</td></tr></tbody></table><h3 id="Project"><a href="#Project" class="headerlink" title="Project"></a>Project</h3><p>每个 Gradle 项目都由一个或多个 Project 构成，每个 Project 又都由 Task 构成。一个 <code>build.gradle</code>文件便是对一个 Project 对象的配置。在 Android 项目中，根目录会存在一个<code>build.gradle</code>文件，每个模块下也会有一个该文件。</p><p>在构建脚本中调用的没有在构建脚本中定义的方法和属性都委派给 Project 对象，比如<code>project.copy()</code>等价于<code>copy()</code>、<code>project.buildDir</code>等价于<code>buildDir</code> </p><h3 id="Task"><a href="#Task" class="headerlink" title="Task"></a>Task</h3><h4 id="创建任务"><a href="#创建任务" class="headerlink" title="创建任务"></a>创建任务</h4><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">task hello {</span><br><span class="line">  doLast {</span><br><span class="line">    println <span class="string">'Hello World!'</span></span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//直接用任务名称</span></span><br><span class="line"><span class="keyword">def</span> Task helloo = task(helloo)</span><br><span class="line">helloo.doLast {</span><br><span class="line">  println <span class="string">'Helloo World!'</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//声明任务配置</span></span><br><span class="line"><span class="keyword">def</span> Task hellooo = task(hellooo, <span class="string">group:</span> BasePlugin.BUILD_GROUP)</span><br><span class="line">hellooo.doLast {</span><br><span class="line">  println <span class="string">'Hellooo World!'</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//使用 TaskContainer 的 create 方法创建，以上三种方式最终都会调用该方法</span></span><br><span class="line">tasks.create(<span class="string">name:</span> <span class="string">'helloooo'</span>) {</span><br><span class="line">  doLast {</span><br><span class="line">    println <span class="string">'helloooo World!'</span></span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输入<code>gradle -q hello*</code>之后的结果为，说明成功创建任务</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Hello World!</span><br><span class="line">Helloo World!</span><br><span class="line">Hellooo World!</span><br><span class="line">helloooo World!</span><br></pre></td></tr></tbody></table></figure><h4 id="任务顺序"><a href="#任务顺序" class="headerlink" title="任务顺序"></a>任务顺序</h4><p>通过<code>dependsOn</code>指定任务的依赖，通过一个例子理解</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">task hello {</span><br><span class="line">  println <span class="string">'hello'</span></span><br><span class="line">    </span><br><span class="line">  doFirst {</span><br><span class="line">    println <span class="string">'hello first'</span></span><br><span class="line">  }</span><br><span class="line">  doLast {</span><br><span class="line">    println <span class="string">'Hello last'</span></span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">task go(<span class="string">dependsOn:</span> hello) {</span><br><span class="line">  println <span class="string">'go'</span></span><br><span class="line"></span><br><span class="line">  doLast {</span><br><span class="line">    println <span class="string">'go last 0'</span></span><br><span class="line">  } </span><br><span class="line"></span><br><span class="line">  doFirst {</span><br><span class="line">    println <span class="string">'go first'</span></span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  doLast {</span><br><span class="line">    println <span class="string">'go last 1'</span></span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输入<code>gradle -q go</code>之后的输出结果为</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">hello</span><br><span class="line">go</span><br><span class="line">hello first</span><br><span class="line">Hello last</span><br><span class="line">go first</span><br><span class="line">go last 0</span><br><span class="line">go last 1</span><br></pre></td></tr></tbody></table></figure><h4 id="排除任务"><a href="#排除任务" class="headerlink" title="排除任务"></a>排除任务</h4><p>在命令行后添加<code>-x go</code>来排除任务 go，输入<code>gradle hello -x go</code>之后</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">> Task :hello</span><br><span class="line">hello</span><br><span class="line">hello first</span><br><span class="line">hello last</span><br><span class="line"></span><br><span class="line">BUILD SUCCESSFUL <span class="keyword">in</span> 1s</span><br><span class="line">1 actionable task: 1 executed</span><br></pre></td></tr></tbody></table></figure><h4 id="动态任务"><a href="#动态任务" class="headerlink" title="动态任务"></a>动态任务</h4><p>可以通过拓展方法<code>times</code>动态创建任务</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line"><span class="number">3.</span>times {</span><br><span class="line">  count -> task <span class="string">"task$count"</span> {</span><br><span class="line">    doLast {</span><br><span class="line">      println <span class="string">"task $count"</span></span><br><span class="line">    }</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输入<code>gradle -q task0</code>之后将输出<code>task 0</code>，已创建的三个任务中的一个</p><h4 id="任务属性"><a href="#任务属性" class="headerlink" title="任务属性"></a>任务属性</h4><p>标准属性有<code>group</code>、<code>description</code>等，除此之外的自定义属性需添加<code>ext</code>前缀</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">task hello {</span><br><span class="line">  group = <span class="string">'group0'</span></span><br><span class="line">  description = <span class="string">'description'</span></span><br><span class="line">  ext.myTitle = <span class="string">'title'</span></span><br><span class="line">  ext.myId = <span class="number">9527</span></span><br><span class="line"></span><br><span class="line">  doLast {</span><br><span class="line">    println <span class="string">"任务分组属性: $group"</span></span><br><span class="line">    println <span class="string">"任务描述:属性 $description"</span></span><br><span class="line">    println <span class="string">"自定义Title属性: $myTitle"</span></span><br><span class="line">    println <span class="string">"自定义Id属性: $myId"</span></span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出正常</p><h2 id="Groovy"><a href="#Groovy" class="headerlink" title="Groovy"></a>Groovy</h2><p><img src="/Gradle-构建工具详解/groovy.png" alt></p><p>Gadle 使用 Groovy 的 DSL 编写，Groovy 是 Apache 推出的 JVM 语言。Groovy 的学习可以参考其<a href="http://cndoc.github.io/groovy-doc-cn/" target="_blank" rel="noopener">官方文档</a>，写得相当友好。Groovy 可以与 Java 无缝连接，甚至可以在其中直接使用 Java 语法，Java 中调用 Groovy 也相当方便。此处只介绍一些与 Java 不同的地方。</p><p>Groovy 会默认导入以下包，不需要显示导入</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">java.io.*</span><br><span class="line">java.lang.*</span><br><span class="line">java.math.BigDecimal</span><br><span class="line">java.math.BigInteger</span><br><span class="line">java.net.*</span><br><span class="line">java.util.*</span><br><span class="line">groovy.lang.*</span><br><span class="line">groovy.util.*</span><br></pre></td></tr></tbody></table></figure><p>可以导入 SDK 之后在 IDE 中编写 Groovy 程序，也可以在 <code>build.gradle</code> 中编写代码，在 task</p><p>中调用，使用 gradle 命令运行。以下的例子采用后一种方法。</p><h3 id="变量与方法"><a href="#变量与方法" class="headerlink" title="变量与方法"></a>变量与方法</h3><p>使用 def 关键字来定义变量和方法，可以不指定变量的类型，默认访问修饰符为 public。Groovy 中使用双引号定义字符串的时候类型为<code>GString</code>而非<code>java.lang.String</code>，因此可以使用<code>$</code>输出表达式结果</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">task t {</span><br><span class="line">  <span class="comment">//定义变量，可以使用 def，也可以使用具体类型，或者两者结合</span></span><br><span class="line">  <span class="keyword">def</span> a = <span class="number">0</span></span><br><span class="line">  <span class="keyword">def</span> <span class="keyword">int</span> b = <span class="number">1</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">//定义字符串，同 dart </span></span><br><span class="line">  String s = <span class="string">"s"</span></span><br><span class="line">  String ss = <span class="string">'ss'</span></span><br><span class="line">  String sss = <span class="string">"""first row</span></span><br><span class="line"><span class="string">  second row"""</span></span><br><span class="line"></span><br><span class="line">  println <span class="string">"95-27=${minus(95,27)}"</span></span><br><span class="line">  println <span class="string">"95+27=${add 95,27}"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//指定返回类型则 def 可省略，且参数类型可省略</span></span><br><span class="line"><span class="comment">//不使用 return 则返回最后一行</span></span><br><span class="line"><span class="keyword">int</span> minus(a, b) {</span><br><span class="line">  println <span class="string">"before return"</span></span><br><span class="line">  a - b</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//定义方法</span></span><br><span class="line"><span class="keyword">def</span> add(<span class="keyword">int</span> a, <span class="keyword">int</span> b) {</span><br><span class="line">  <span class="keyword">return</span> a + b</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">before <span class="built_in">return</span></span><br><span class="line">95-27=68</span><br><span class="line">95+27=122</span><br></pre></td></tr></tbody></table></figure><h3 id="对象"><a href="#对象" class="headerlink" title="对象"></a>对象</h3><p>Groovy 中的类与 Java 类似，不过由于没有访问修饰符，默认为<code>public</code>，要想实现 Java 默认的包访问权限（<code>default</code>），可以使用注解<code>@PackageScope</code>。对于没有可见性修饰的变量，Groovy 会隐式提供<code>setter/getter</code>方法。以下的两个类是等价的</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">task t {</span><br><span class="line">  <span class="keyword">def</span> object = <span class="keyword">new</span> ClassInGroovy()</span><br><span class="line">  object.name = <span class="string">"Jack"</span></span><br><span class="line">  println <span class="string">"${object.name}"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ClassInJava</span> {</span></span><br><span class="line">  <span class="keyword">public</span> String name;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> String getName() {</span><br><span class="line">    <span class="keyword">return</span> name;</span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="keyword">public</span> <span class="keyword">void</span> setName(String name) {</span><br><span class="line">    <span class="keyword">this</span>.name = name;</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ClassInGroovy</span> {</span></span><br><span class="line">  String name</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>此外 Groovy 使用<code>asType</code>函数进行类型转换，支持<code>object.with{ }</code>进行级联操作，支持使用<code>?.</code>进行非空操作，都是些很眼熟的语法特性</p><h3 id="闭包"><a href="#闭包" class="headerlink" title="闭包"></a>闭包</h3><p>闭包在 Groovy 中是<code>groovy.lang.Closure</code>的实例，类似于 Dart 中的函数是 Function 的实例，因此可以像变量一样传递，其语法定义为</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//闭包的参数为可选项</span></span><br><span class="line"><span class="keyword">def</span> closure = { [closureParameters -> ] statements }</span><br></pre></td></tr></tbody></table></figure><p>闭包跟函数不同的地方在于其可以访问外部变量</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">task t {</span><br><span class="line">  <span class="keyword">def</span> str = <span class="string">"hello"</span></span><br><span class="line">  <span class="keyword">def</span> closure0 = {</span><br><span class="line">    println str  </span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="keyword">def</span> closure1 = { String name, <span class="keyword">int</span> t -> </span><br><span class="line">    println <span class="string">"before print"</span></span><br><span class="line">    println <span class="string">"$name call $t times"</span></span><br><span class="line">  }</span><br><span class="line"></span><br><span class="line">  <span class="comment">//调用，call 可省略</span></span><br><span class="line">  closure0.call()</span><br><span class="line">  closure1(<span class="string">"Jack"</span>, <span class="number">10</span>)</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>与 Java 中的 Lambda 表达式类似，闭包可以当做参数传递，当闭包为最后一个参数时可写在调用的括号后</p><h3 id="文件读取"><a href="#文件读取" class="headerlink" title="文件读取"></a>文件读取</h3><p>相较于 Java，Groovy 的文件读写非常简洁友好。在当前文件夹新建一个文件<code>静夜思.txt</code></p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">task t {</span><br><span class="line">  <span class="keyword">def</span> path = <span class="string">"静夜思.txt"</span></span><br><span class="line">  <span class="keyword">def</span> file = <span class="keyword">new</span> File(path).eachLine { line -></span><br><span class="line">    println line</span><br><span class="line">  }</span><br><span class="line">  <span class="comment">//更简洁</span></span><br><span class="line">  println file.text</span><br><span class="line"></span><br><span class="line">  <span class="comment">//写入，该方法会打印并覆盖原来的内容</span></span><br><span class="line">  file.withPrintWriter {</span><br><span class="line">    it.println <span class="string">"表达了诗人对家乡的思念"</span></span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h3><p>Groovy 中的流程控制语句（比如<code>if/else</code>、<code>for/in</code>、<code>switch/case</code>）、数据结构（比如<code>List</code>、<code>Map</code>）与 Java/Dart 类似，在此便不过多费笔墨，参考官方文档即可。</p><h2 id="Gradle-Wrapper"><a href="#Gradle-Wrapper" class="headerlink" title="Gradle Wrapper"></a>Gradle Wrapper</h2><p>Gradle 包装器。为了应对团队开发中 Gradle 环境和版本的差异会对编译结果带来的不确定性，使用 Gradle Wrapper，它是一个脚本，可以指定构建版本、快速运行项目，从而达到标准化、提到开发效率。Android Studio 新建项目时自带 Gradle Wrapper，因此 Android 开发者很少单独下载安装 Gradle</p><p>Gradle Wrapper 的工作流程如下图</p><p><img src="https://docs.gradle.org/current/userguide/img/wrapper-workflow.png" alt></p><p>使用 Gradle Wrapper 启动 Gradle 之后，如果指定版本的 Gradle 没有被下载关联，会先从官方仓库下载到用户本地，进行解包并执行批处理文件。后续的构建运行都会重用这个解包的运行时安装程序</p><h3 id="构建-Gradle-Wrapper"><a href="#构建-Gradle-Wrapper" class="headerlink" title="构建 Gradle Wrapper"></a>构建 Gradle Wrapper</h3><p>Gradle 内置 Wrapper Task，执行 Wrapper Task 就可以在项目目录中生成对应的目录文件。在项目根目录执行<code>gradle wrapper</code> 命令即可。之后根目录的文件结构如下</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">├── gradle</span><br><span class="line">│   └── wrapper</span><br><span class="line">│       ├── gradle-wrapper.jar</span><br><span class="line">│       └── gradle-wrapper.properties</span><br><span class="line">├── gradlew</span><br><span class="line">└── gradlew.bat</span><br></pre></td></tr></tbody></table></figure><p>文件含义为</p><ul><li>gradle-wrapper.jar ：包含 Gradle 运行时的逻辑代码。</li><li>gradle-wrapper.properties ：负责配置包装器运行时行为的属性文件</li><li>gradlew：Linux 平台下，用于执行 Gralde 命令的包装器脚本。</li><li>gradlew.bat：Windows 平台下，用于执行 Gradle 命令的包装器脚本。</li></ul><p>查看<code>.properties</code>文件</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">distributionBase=GRADLE_USER_HOME</span><br><span class="line">distributionPath=wrapper/dists</span><br><span class="line">distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-bin.zip</span><br><span class="line">zipStoreBase=GRADLE_USER_HOME</span><br><span class="line">zipStorePath=wrapper/dists</span><br></pre></td></tr></tbody></table></figure><p>属性含义为</p><ul><li>distributionBase：Gradle 解包后存储的主目录。</li><li>distributionPath：distributionBase 指定目录的子目录。distributionBase+distributionPath 为 Gradle 解包后的存放位置。</li><li>distributionUrl：Gradle 发行版压缩包的下载地址。</li><li>zipStoreBase：Gradle 压缩包存储主目录。</li><li>zipStorePath：zipStoreBase 指定目录的子目录。zipStoreBase+zipStorePath 为 Gradle 压缩包的存放位置。</li></ul><p>如果官方包发行版下载缓慢，可以手动更改<code>distributionUrl</code>为可用地址</p><h3 id="使用-Gradle-Wrapper"><a href="#使用-Gradle-Wrapper" class="headerlink" title="使用 Gradle Wrapper"></a>使用 Gradle Wrapper</h3><p>使用<code>gradlew.bar</code>代替<code>gradle</code>运行 Gradle Project，首次使用会下载 Gradle 到配置文件指定的位置，作者的路径为<code>C:\Users\23033\.gradle\wrapper\dists\gradle-5.4.1-bin\e75iq110yv9r9wt1a6619x2xm\gradle-5.4.1</code></p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">PS D:\Work\Gradle\gradle_demo> .\gradlew.bat t</span><br><span class="line">Downloading https://services.gradle.org/distributions/gradle-5.4.1-bin.zip</span><br><span class="line">...................................................................................</span><br><span class="line"></span><br><span class="line">Hello World!</span><br></pre></td></tr></tbody></table></figure><p>再次使用该命令便不会重复下载。升级 Gradle 版本可以通过<code>gradlew wrapper –gradle-version 5.*.*</code>命令实现</p><h2 id="Gradle-插件"><a href="#Gradle-插件" class="headerlink" title="Gradle 插件"></a>Gradle 插件</h2><p>Gradle 中的插件可分为两类</p><ul><li>脚本插件：额外的构建脚本，类似于一个 build.gradle</li><li>对象插件：又叫二进制插件，是实现了 Plugin 接口的类</li></ul><p>应用插件又分为两个步骤，一是解析插件，二是通过<code>apply()</code>把插件应用到项目中。</p><h3 id="脚本插件"><a href="#脚本插件" class="headerlink" title="脚本插件"></a>脚本插件</h3><p>在根目录新建一个<code>other.gradle</code>文件，内容如下</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">ext {</span><br><span class="line">  otherVersion = <span class="string">'1.0'</span></span><br><span class="line">  otherUrl = <span class="string">'https://febers.github.io'</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>将<code>build.gradle</code>内容修改为</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">apply <span class="string">from:</span> <span class="string">'other.gradle'</span></span><br><span class="line">task t {</span><br><span class="line">  println <span class="string">"版本为: ${otherVersion},地址为: ${otherUrl}"</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出结果</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">PS D:\Work\Gradle\gradle_demo> .\gradlew.bat t</span><br><span class="line"></span><br><span class="line">> Configure project :</span><br><span class="line">版本为: 1.0,地址为: https://febers.github.io</span><br></pre></td></tr></tbody></table></figure><h3 id="对象插件"><a href="#对象插件" class="headerlink" title="对象插件"></a>对象插件</h3><p>对象插件是实现了<code>org.gradle.api.plugin<Project></code>接口的插件，又可分为内部插件和第三方插件</p><h4 id="内部插件"><a href="#内部插件" class="headerlink" title="内部插件"></a>内部插件</h4><p>使用以下方法应用 Java 插件（因为默认导入<code>org.gradle.api.plugin</code>包，所以可以去掉包名）</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">apply <span class="string">plugin:</span> org.gradle.api.plugins.JavaPlugin</span><br><span class="line">apply <span class="string">plugin:</span> JavaPlugin<span class="comment">//去掉包名</span></span><br><span class="line">apply <span class="string">plugin:</span> <span class="string">'java'</span><span class="comment">//使用 pluginid（实现 plugin 接口的插件的属性）</span></span><br><span class="line">apply <span class="string">plugin:</span> <span class="string">'cpp'</span>    <span class="comment">//Gradle 中含有大量插件</span></span><br></pre></td></tr></tbody></table></figure><h4 id="第三方插件"><a href="#第三方插件" class="headerlink" title="第三方插件"></a>第三方插件</h4><p>一般为 jar 文件，通过<code>buildscript</code>配置，此处引入 Android Gradle 插件</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">buildscript {</span><br><span class="line">  repositories {</span><br><span class="line">    google()</span><br><span class="line">  }</span><br><span class="line">  dependencies {</span><br><span class="line">    classpath <span class="string">'com.android.tools.build:gradle:3.4.1'</span></span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line">apply <span class="string">plugin:</span> <span class="string">'com.android.application'</span></span><br></pre></td></tr></tbody></table></figure><p>如果第三方插件被托管到 <a href="Gradle - Pluginshttps://plugins.gradle.org/">Gradle - Plugins</a>，也可以不使用<code>buildscript</code></p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">plugins {</span><br><span class="line">  id <span class="string">"com.quittle.setup-android-sdk"</span> version <span class="string">"1.2.0"</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h4 id="自定义对象插件"><a href="#自定义对象插件" class="headerlink" title="自定义对象插件"></a>自定义对象插件</h4><p>Plugin 接口中定义了一个<code>apply</code>方法，重写该方法，在其中通过传进来的参数<code>Object o</code>（实际为 Project 类型）调用 task 新建一个任务</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">MyPlugin</span> <span class="keyword">implements</span> <span class="title">Plugin</span> {</span></span><br><span class="line">  <span class="meta">@Override</span></span><br><span class="line">  <span class="keyword">void</span> apply(Object o) {</span><br><span class="line">    o.task(<span class="string">"myTask"</span>) {</span><br><span class="line">      println <span class="string">"This is a custom task in custom plugin"</span></span><br><span class="line">    }</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line">apply <span class="string">plugin:</span> MyPlugin</span><br></pre></td></tr></tbody></table></figure><p>输入命令<code>gradle myTask</code>即可验证</p><h2 id="Android-中的-Gradle"><a href="#Android-中的-Gradle" class="headerlink" title="Android 中的 Gradle"></a>Android 中的 Gradle</h2><p>为了支持 Android 项目的构建，Google 为 Gradle 编写了 Android 插件，组成 Android 构建系统。Android Studio+Gradle 是目前最流行的 Android 开发构建环境。关于 Android 构建配置可查阅官方文档 <a href="https://developer.android.com/studio/build?hl=zh-cn" target="_blank" rel="noopener">配置构建</a></p><h3 id="模块类型"><a href="#模块类型" class="headerlink" title="模块类型"></a>模块类型</h3><p>Android Studio 中的每个项目包含一个或多个含有源代码文件和资源文件的模块，这些模块可以独立构建测试，模块类型包含以下几种</p><ul><li>Android 应用程序模块：可能依赖于库模块，构建系统会将其生成一个 apk 文件</li><li>Android 库模块：包含可重用的特定于 Android 的代码和资源，构建系统将其生成一个 aar 文件</li><li>App 引擎模块：包含应用程序引擎继承的代码和资源</li><li>Java 库模块：包含可重用的代码，构建系统将其生成一个 jar 文件</li></ul><h3 id="项目结构"><a href="#项目结构" class="headerlink" title="项目结构"></a>项目结构</h3><p>在 Android Studio 中，Android 项目视图如下，以开发者个人项目 <a href="https://github.com/Febers/UESTC_BBS" target="_blank" rel="noopener">UESTC_BBS</a> 为例</p><p><img src="/Gradle-构建工具详解/项目结构.png" alt></p><p>所有构建稳健位于 Gradle Scripts 层级下，文件作用如下</p><ul><li>项目 build.gradle：配置项目整体属性，比如指定的代码仓库、依赖的 Gradle 版本等</li><li>模块 build.gradle：配置当前模块的编译参数</li><li>gradle-wrapper-properties：配置 Gradle Wrapper</li><li>gradle-properties：配置 Gradle</li><li>setting.gradle：配置 Gradle 的多项目管理</li><li>local.properties：存放 Android 项目的私有属性配置，如 SDK 路径</li><li>multiDexKeep.pro、proguard-rules.pro：可选的混淆文件，用于配置放置在主 Dex 的类、声明避免混淆的类</li></ul><h3 id="项目-build-gradle"><a href="#项目-build-gradle" class="headerlink" title="项目 build.gradle"></a>项目 build.gradle</h3><p>典型的项目<code>build.gradle</code>文件如下</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">buildscript {</span><br><span class="line">    ext.kotlin_version = <span class="string">'1.3.10'</span></span><br><span class="line">    repositories {</span><br><span class="line">        mavenCentral()</span><br><span class="line">        google()</span><br><span class="line">        jcenter()</span><br><span class="line">    }</span><br><span class="line">    dependencies {</span><br><span class="line">        classpath <span class="string">'com.android.tools.build:gradle:3.4.0'</span></span><br><span class="line">        classpath <span class="string">"org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"</span></span><br><span class="line">        classpath <span class="string">"org.jetbrains.kotlin:kotlin-android-extensions:$kotlin_version"</span></span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">allprojects {</span><br><span class="line">    repositories {</span><br><span class="line">        google()</span><br><span class="line">        jcenter()</span><br><span class="line">        mavenCentral()</span><br><span class="line">        maven { url <span class="string">'https://jitpack.io'</span> }</span><br><span class="line">        maven { url <span class="string">"https://maven.google.com"</span> }</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">task clean(<span class="string">type:</span> Delete) {</span><br><span class="line">    delete rootProject.buildDir</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>其中<code>google()</code>是配置 Google 的 Maven 仓库，<code>maven { url "https://maven.google.com" }</code>同理</p><h3 id="模块-build-gradle"><a href="#模块-build-gradle" class="headerlink" title="模块 build.gradle"></a>模块 build.gradle</h3><p>典型的模块<code>build.gradle</code>文件如下</p><figure class="highlight groovy"><table><tbody><tr><td class="code"><pre><span class="line">apply <span class="string">plugin:</span> <span class="string">'com.android.application'</span></span><br><span class="line">apply <span class="string">plugin:</span> <span class="string">'kotlin-android'</span></span><br><span class="line">apply <span class="string">plugin:</span> <span class="string">'kotlin-android-extensions'</span></span><br><span class="line">apply <span class="string">plugin:</span> <span class="string">'kotlin-kapt'</span></span><br><span class="line">ext.anko_version = <span class="string">'0.10.5'</span></span><br><span class="line"></span><br><span class="line">dependencies {</span><br><span class="line">    implementation fileTree(<span class="string">include:</span> [<span class="string">'*.jar'</span>], <span class="string">dir:</span> <span class="string">'libs'</span>)</span><br><span class="line">    implementation <span class="string">"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"</span></span><br><span class="line">    implementation <span class="string">'com.android.support:multidex:1.0.3'</span></span><br><span class="line">    implementation <span class="string">'androidx.appcompat:appcompat:1.0.2'</span></span><br><span class="line">    testImplementation <span class="string">'junit:junit:4.12'</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">android {</span><br><span class="line">    compileSdkVersion <span class="number">28</span></span><br><span class="line">    defaultConfig {</span><br><span class="line">        applicationId <span class="string">"com.febers.uestc_bbs"</span></span><br><span class="line">        minSdkVersion <span class="number">17</span></span><br><span class="line">        targetSdkVersion <span class="number">28</span></span><br><span class="line">        versionCode <span class="number">12</span></span><br><span class="line">        versionName <span class="string">"1.1.4"</span></span><br><span class="line">        testInstrumentationRunner <span class="string">"androidx.test.runner.AndroidJUnitRunner"</span></span><br><span class="line">        multiDexEnabled <span class="literal">true</span></span><br><span class="line">        multiDexKeepProguard file(<span class="string">'multiDexKeep.pro'</span>) <span class="comment">// keep specific classes using proguard syntax</span></span><br><span class="line">    }</span><br><span class="line">    compileOptions {</span><br><span class="line">        sourceCompatibility JavaVersion.VERSION_1_8</span><br><span class="line">        targetCompatibility JavaVersion.VERSION_1_8</span><br><span class="line">    }</span><br><span class="line">    buildTypes {</span><br><span class="line">        release {</span><br><span class="line">            minifyEnabled <span class="literal">false</span></span><br><span class="line">            proguardFiles getDefaultProguardFile(<span class="string">'proguard-android.txt'</span>), <span class="string">'proguard-rules.pro'</span></span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>第一行 apply 的是一个<code>application</code>，说明当前模块为一个应用程序模块，Gradle 的 Android 插件分为以下几种</p><ul><li>应用程序插件：插件 id 为<code>com.android.application</code>，构建生成 apk</li><li>库插件：插件 id 为<code>com.android.library</code>，构建生成 aar</li><li>测试插件：插件 id 为<code>com.android.test</code>，用于测试其他模块</li><li>feature 插件：插件 id 为<code>com.android.feature</code>，用于创建 Android Instant App</li><li>instant App 插件：插件 id 为<code>com.android.instantapp</code>，是 Android Instant App 的入口</li></ul><p>很多属性都可以望文生义，此处不再赘述。</p><hr><p>参考</p><p><a href="http://liuwangshu.cn/application/android-gradle/1-gradle-plug-in.html" target="_blank" rel="noopener">Gradle的Android插件入门</a></p><p><a href="http://liuwangshu.cn/tags/Gradle%E6%A0%B8%E5%BF%83%E6%80%9D%E6%83%B3/" target="_blank" rel="noopener">Gradle核心思想</a></p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Gradle 是一个基于 JVM 的自动化构建工具，使用 Groovy  DSL 声明配置。现代软件开发包含众多步骤，包括编译、测试、打包等，如果需要手动重复每一过程，将会耗费大量时间、增加出错概率，项目自动化应运而生。在此之前，常见的 Java 构建工具包括 Ant、Gant 和 Maven 等，Gradle 结合了以上工具的优点，基于约定大于配置，通用灵活，是 Android 的官方构建工具。本文将介绍 Gradle 的基本知识、Groovy 基本语法以及 Android 开发中的 Gradle 知识。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://plugins.gradle.org/shared-assets/shared/images/gradle-logo-horizontal.svg&quot; alt&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="Gradle" scheme="http://yoursite.com/categories/Gradle/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
      <category term="Java" scheme="http://yoursite.com/tags/Java/"/>
    
      <category term="Gradle" scheme="http://yoursite.com/tags/Gradle/"/>
    
      <category term="构建工具" scheme="http://yoursite.com/tags/%E6%9E%84%E5%BB%BA%E5%B7%A5%E5%85%B7/"/>
    
      <category term="Groovy" scheme="http://yoursite.com/tags/Groovy/"/>
    
  </entry>
  
  <entry>
    <title>Kotlin 委托属性详解</title>
    <link href="http://yoursite.com/Kotlin-%E5%A7%94%E6%89%98%E5%B1%9E%E6%80%A7%E8%AF%A6%E8%A7%A3/"/>
    <id>http://yoursite.com/Kotlin-委托属性详解/</id>
    <published>2019-05-21T08:52:14.000Z</published>
    <updated>2019-06-02T05:02:28.000Z</updated>
    
    <content type="html"><![CDATA[<p>委托属性算是 Kotlin 语言中的高级特性，初次接触可能毫无头绪，再次接触还是一脸懵逼。只有在深入理解其语言特性和实现原理之后，才能对这一甜之又甜的“语法糖”有所认识，从而极大提高代码效率。在笔者的项目 <a href="https://github.com/Febers/UESTC_BBS" target="_blank" rel="noopener">UESTC_BBS</a> 中，对自带 SharedPreferences 的封装 <a href="https://github.com/Febers/UESTC_BBS/blob/master/app/src/main/java/com/febers/uestc_bbs/utils/PreferenceUtils.kt" target="_blank" rel="noopener">PreferenceUtils </a>就使用了委托属性，故一直想找机会写一篇笔记类型的文章将其记录下来。委托属性的基础是<strong>委托</strong>，一种设计模式，操作的对象不用自己执行，而是委托给另一个辅助对象。<a id="more"></a></p><h2 id="委托"><a href="#委托" class="headerlink" title="委托"></a>委托</h2><h3 id="基本使用"><a href="#基本使用" class="headerlink" title="基本使用"></a>基本使用</h3><p>定义一个委托属性的基本语法为 <code>val/var <属性名>: <类型> by <表达式></code>，在 <em>by</em> 后面的表达式即为<em>委托</em>， 属性对应的 <code>get()</code>与<code>set()</code>会被委托给它的 <code>getValue()</code> 与 <code>setValue()</code> 方法。 </p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Example</span> </span>{</span><br><span class="line">    <span class="keyword">var</span> p: String <span class="keyword">by</span> Delegate()</span><br><span class="line"></span><br><span class="line">    <span class="keyword">companion</span> <span class="keyword">object</span> {</span><br><span class="line">        <span class="meta">@JvmStatic</span></span><br><span class="line">        <span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span><<span class="type">String</span>>)</span></span> {</span><br><span class="line">            <span class="keyword">var</span> e = Example()</span><br><span class="line">            println(e.p)</span><br><span class="line">            e.p = <span class="string">"newValue"</span></span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Delegate</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> <span class="title">getValue</span><span class="params">(thisRef: <span class="type">Any</span>?, property: <span class="type">Any</span>)</span></span>: String {</span><br><span class="line">        <span class="keyword">return</span> <span class="string">"<span class="variable">$thisRef</span>, thank you for delegating '<span class="variable">$property</span>' to me!"</span></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">operator</span> <span class="function"><span class="keyword">fun</span> <span class="title">setValue</span><span class="params">(thisRef: <span class="type">Any</span>?, property: <span class="type">Any</span>, value: <span class="type">String</span>)</span></span> {</span><br><span class="line">        println(<span class="string">"<span class="variable">$value</span> has been assigned to '<span class="variable">$property</span>' in <span class="variable">$thisRef</span>."</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>上述代码中，Delegate 内方法的参数 property 原本为 KProperty<*> 接口类型，为了手动调用其方法，改成 Any 以实现传参。</p><p>控制台输出结果为</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Example@1175e2db, thank you <span class="keyword">for</span> delegating <span class="string">'p'</span> to me!</span><br><span class="line">newValue has been assigned to <span class="string">'p'</span> <span class="keyword">in</span> Example@1175e2db.</span><br></pre></td></tr></tbody></table></figure><p>可以看到属性 p 委托给了 Delegate() 对象实例，按照约定，该对象必须声明具有<code>getValue</code>、<code>setValue</code>方法，且方法参数个数必须大于2、3。</p><p>为了更清楚的了解这一过程，可以将代码改写成另一种形式</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Example</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="keyword">var</span> delegate: Delegate = Delegate()</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">var</span> p: String</span><br><span class="line">        <span class="keyword">set</span>(value) {</span><br><span class="line">            delegate.setValue(thisRef = <span class="keyword">this</span>, property = delegate, value = value)</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">get</span>() = delegate.getValue(thisRef = <span class="keyword">this</span>, property = delegate)</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>当我们使用 p 时调用其<code>get</code>方法，给 p 赋值时调用其<code>set</code>方法，并且都是通过委托对象 delegate 实现。</p><h2 id="标准委托"><a href="#标准委托" class="headerlink" title="标准委托"></a>标准委托</h2><h3 id="lazy"><a href="#lazy" class="headerlink" title="lazy"></a>lazy</h3><p>函数<code>lazy</code>接收一个 lambda 表达式并返回一个 <code>Lazy <T></code> 实例，默认情况下其线程安全</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//方法签名</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">actual</span> <span class="function"><span class="keyword">fun</span> <span class="type"><T></span> <span class="title">lazy</span><span class="params">(initializer: ()</span></span> -> T): Lazy<T> = SynchronizedLazyImpl(initializer)</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> s: String <span class="keyword">by</span> lazy {</span><br><span class="line">println(<span class="string">"get"</span>)</span><br><span class="line"><span class="string">"hello"</span></span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">println(s)</span><br><span class="line">println(s)</span><br></pre></td></tr></tbody></table></figure><p>只有在第一次调用 s 时才会执行传递给 <code>lazy()</code> 的 lambda 表达式并返回一个记录下来的结果， 后续调用 <code>get()</code> 只会返回记录的结果。底层原理在于函数签名中的<code>SynchronizedLazyImpl</code>方法，其中会检查变量的值，判断其是否为默认值，如果是则执行初始化函数，否则直接返回结果，具体代码可以查阅<code>LazyJVM.kt</code>文件。</p><p>控制台输出为</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">get</span><br><span class="line">hello</span><br><span class="line">hello</span><br></pre></td></tr></tbody></table></figure><h3 id="observable"><a href="#observable" class="headerlink" title="observable"></a>observable</h3><p>字面意思，用委托的方式来定义一个可观察属性。该函数的方法签名为</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">inline</span> <span class="function"><span class="keyword">fun</span> <span class="type"><T></span> <span class="title">observable</span><span class="params">(initialValue: <span class="type">T</span>, <span class="keyword">crossinline</span> onChange: (<span class="type">property</span>: <span class="type">KProperty</span><*>, oldValue: <span class="type">T</span>, newValue: <span class="type">T</span>)</span></span> -> <span class="built_in">Unit</span>):</span><br><span class="line">            ReadWriteProperty<Any?, T></span><br></pre></td></tr></tbody></table></figure><p>其接收两个参数，第一个为默认值，第二个为 lambda 表达式，位于<code>Delegates.kt</code>，具体使用</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> name: String <span class="keyword">by</span> Delegates.observable(<span class="string">"initialValue"</span>) {</span><br><span class="line">property, oldValue, newValue -></span><br><span class="line">println(<span class="string">"<span class="variable">$oldValue</span> -> <span class="variable">$newValue</span>"</span>)</span><br><span class="line">}</span><br><span class="line">name = <span class="string">"newValue0"</span></span><br><span class="line">name = <span class="string">"newValue1"</span></span><br></pre></td></tr></tbody></table></figure><p>控制台输出</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">initialValue -> newValue0</span><br><span class="line">newValue0 -> newValue1</span><br></pre></td></tr></tbody></table></figure><h3 id="Storing"><a href="#Storing" class="headerlink" title="Storing"></a>Storing</h3><p>将对象的属性委托值 map 中，用于解析 JSON 或者其他动态工作，不过应用较少</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">val</span> user = User(mapOf(<span class="string">"name"</span> to <span class="string">"Jack"</span>, <span class="string">"age"</span> to <span class="number">20</span>))</span><br><span class="line">println(user.name)</span><br><span class="line">println(user.age)</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">User</span></span>(<span class="keyword">private</span> <span class="keyword">val</span> map: Map<String, Any?>) {</span><br><span class="line">    <span class="keyword">val</span> name: String <span class="keyword">by</span> map</span><br><span class="line">    <span class="keyword">val</span> age: <span class="built_in">Int</span> <span class="keyword">by</span> map</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="典型应用"><a href="#典型应用" class="headerlink" title="典型应用"></a>典型应用</h2><p>封装一个 SharedPreferences（简称 SP） 是 Android 开发中经常要做的事，因为直接调用 SP 足够繁琐。如果是 Java 代码，则代码基本如下</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">PreferencesUtil</span> </span>{</span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> PreferencesUtil sInstance;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">(Context context)</span> </span>{</span><br><span class="line">        <span class="keyword">if</span> (sInstance == <span class="keyword">null</span>) {</span><br><span class="line">            sInstance = <span class="keyword">new</span> PreferencesUtil(context);</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> PreferencesUtil <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line">        <span class="keyword">if</span> (sInstance == <span class="keyword">null</span>) <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"Uninitialized."</span>);</span><br><span class="line"></span><br><span class="line">        <span class="keyword">return</span> sInstance;</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> SharedPreferences mSp;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">private</span> <span class="title">PreferencesUtil</span><span class="params">(Context context)</span> </span>{</span><br><span class="line">        mSp = PreferenceManager.getDefaultSharedPreferences(context);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> String <span class="title">getString</span><span class="params">(String key, String defValue)</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> mSp.getString(key, defValue);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">putString</span><span class="params">(String key, String value)</span> </span>{</span><br><span class="line">        mSp.edit().putString(key, value).apply();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">int</span> <span class="title">getInt</span><span class="params">(String key, <span class="keyword">int</span> defValue)</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> mSp.getInt(key, defValue);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">putInt</span><span class="params">(String key, <span class="keyword">int</span> value)</span> </span>{</span><br><span class="line">        mSp.edit().putInt(key, value).apply();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">getLong</span><span class="params">(String key, <span class="keyword">long</span> defValue)</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> mSp.getLong(key, defValue);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">putLong</span><span class="params">(String key, <span class="keyword">long</span> value)</span> </span>{</span><br><span class="line">        mSp.edit().putLong(key, value).apply();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">float</span> <span class="title">getFloat</span><span class="params">(String key, <span class="keyword">float</span> defValue)</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> mSp.getFloat(key, defValue);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">putFloat</span><span class="params">(String key, <span class="keyword">float</span> value)</span> </span>{</span><br><span class="line">        mSp.edit().putFloat(key, value).apply();</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">getBoolean</span><span class="params">(String key, <span class="keyword">boolean</span> defValue)</span> </span>{</span><br><span class="line">        <span class="keyword">return</span> mSp.getBoolean(key, defValue);</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">putBoolean</span><span class="params">(String key, <span class="keyword">boolean</span> value)</span> </span>{</span><br><span class="line">        mSp.edit().putBoolean(key, value).apply();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>外部调用</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">if</span> (PreferencesUtil.getInstance().getBoolean(Constant.IS_FIRST_LAUNCH, Constant.DEF_IS_FIRST_LAUNCH)) {</span><br><span class="line">    <span class="comment">// Do something first launch, like showing Welcome.</span></span><br><span class="line">    ...</span><br><span class="line"></span><br><span class="line">    PreferencesUtil.getInstance().putBoolean(Constant.IS_FIRST_LAUNCH, <span class="keyword">false</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>使用 Kotlin 的委托属性之后实现就简洁很多</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PreferenceUtils</span><<span class="type">T</span>></span>(<span class="keyword">val</span> context: Context, <span class="keyword">val</span> name: String, <span class="keyword">val</span> <span class="keyword">default</span>: T): ReadWriteProperty<Any?, T> {</span><br><span class="line"></span><br><span class="line">    <span class="keyword">val</span> prefs: SharedPreferences <span class="keyword">by</span> lazy { context.defaultSharedPreferences }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">getValue</span><span class="params">(thisRef: <span class="type">Any</span>?, property: <span class="type">KProperty</span><*>)</span></span>: T {</span><br><span class="line">        <span class="keyword">return</span> findPreference(name, <span class="keyword">default</span>)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">override</span> <span class="function"><span class="keyword">fun</span> <span class="title">setValue</span><span class="params">(thisRef: <span class="type">Any</span>?, property: <span class="type">KProperty</span><*>, value: <span class="type">T</span>)</span></span> {</span><br><span class="line">        putPreference(name, value)</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="type"><T></span> <span class="title">findPreference</span><span class="params">(name: <span class="type">String</span>, <span class="keyword">default</span>: <span class="type">T</span>)</span></span>: T = with(prefs) {</span><br><span class="line">        <span class="keyword">val</span> res: Any = <span class="keyword">when</span> (<span class="keyword">default</span>) {</span><br><span class="line">            <span class="keyword">is</span> <span class="built_in">Long</span> -> getLong(name, <span class="keyword">default</span>)</span><br><span class="line">            <span class="keyword">is</span> <span class="built_in">Int</span> -> getInt(name, <span class="keyword">default</span>)</span><br><span class="line">            <span class="keyword">is</span> String -> getString(name, <span class="keyword">default</span>)</span><br><span class="line">            <span class="keyword">is</span> <span class="built_in">Boolean</span> -> getBoolean(name, <span class="keyword">default</span>)</span><br><span class="line">            <span class="keyword">is</span> <span class="built_in">Float</span> -> getFloat(name, <span class="keyword">default</span>)</span><br><span class="line">            <span class="keyword">else</span> -> <span class="keyword">throw</span> IllegalArgumentException(<span class="string">"This type can't be saved into Preferences"</span>)</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">return</span><span class="symbol">@with</span> res <span class="keyword">as</span> T</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="function"><span class="keyword">fun</span> <span class="type"><T></span> <span class="title">putPreference</span><span class="params">(name: <span class="type">String</span>, value: <span class="type">T</span>)</span></span> = with(prefs.edit()) {</span><br><span class="line">        <span class="keyword">when</span> (value) {</span><br><span class="line">            <span class="keyword">is</span> <span class="built_in">Long</span> -> putLong(name, value)</span><br><span class="line">            <span class="keyword">is</span> <span class="built_in">Int</span> -> putInt(name, value)</span><br><span class="line">            <span class="keyword">is</span> String -> putString(name, value)</span><br><span class="line">            <span class="keyword">is</span> <span class="built_in">Boolean</span> -> putBoolean(name, value)</span><br><span class="line">            <span class="keyword">is</span> <span class="built_in">Float</span> -> putFloat(name, value)</span><br><span class="line">            <span class="keyword">else</span> -> <span class="keyword">throw</span> IllegalArgumentException(<span class="string">"This type can't be saved into Preferences"</span>)</span><br><span class="line">        }.apply()</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>其中接口<code>ReadWriteProperty</code>为系统提供的规范接口，其中定义了<code>getValue/setValue</code>方法。外部调用如下</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> themeCode <span class="keyword">by</span> PreferenceUtils(context, Constant.theme_code, <span class="keyword">default</span> = <span class="number">1</span>)</span><br><span class="line">themeCode = <span class="number">9527</span></span><br></pre></td></tr></tbody></table></figure><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;委托属性算是 Kotlin 语言中的高级特性，初次接触可能毫无头绪，再次接触还是一脸懵逼。只有在深入理解其语言特性和实现原理之后，才能对这一甜之又甜的“语法糖”有所认识，从而极大提高代码效率。在笔者的项目 &lt;a href=&quot;https://github.com/Febers/UESTC_BBS&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;UESTC_BBS&lt;/a&gt; 中，对自带 SharedPreferences 的封装 &lt;a href=&quot;https://github.com/Febers/UESTC_BBS/blob/master/app/src/main/java/com/febers/uestc_bbs/utils/PreferenceUtils.kt&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;PreferenceUtils &lt;/a&gt;就使用了委托属性，故一直想找机会写一篇笔记类型的文章将其记录下来。委托属性的基础是&lt;strong&gt;委托&lt;/strong&gt;，一种设计模式，操作的对象不用自己执行，而是委托给另一个辅助对象。&lt;/p&gt;
    
    </summary>
    
      <category term="Kotlin" scheme="http://yoursite.com/categories/Kotlin/"/>
    
    
      <category term="Kotlin" scheme="http://yoursite.com/tags/Kotlin/"/>
    
      <category term="委托属性" scheme="http://yoursite.com/tags/%E5%A7%94%E6%89%98%E5%B1%9E%E6%80%A7/"/>
    
  </entry>
  
  <entry>
    <title>Dart 反射初识</title>
    <link href="http://yoursite.com/Dart-%E5%8F%8D%E5%B0%84%E5%88%9D%E8%AF%86/"/>
    <id>http://yoursite.com/Dart-反射初识/</id>
    <published>2019-05-20T02:16:44.000Z</published>
    <updated>2019-06-02T04:55:16.000Z</updated>
    
    <content type="html"><![CDATA[<p>在 <a href="https://febers.github.io/Java-反射详解/" target="_blank" rel="noopener">Java 反射详解</a> 一文中，我们知道<code>反射是一种计算机处理方式，是程序可以访问、检测和修改它本身状态或行为的一种能力</code>。换一个角度说，反射可以细分为自省——程序在运行时决定自身结构的能力，以及自我修正——程序在运行时改变自身的能力。Dart 的反射基于 mirror 概念，它指的是反映其他对象的对象，并且目前只支持自省，不支持自我修改。</p><a id="more"></a><h2 id="Mirror"><a href="#Mirror" class="headerlink" title="Mirror"></a>Mirror</h2><h3 id="Example"><a href="#Example" class="headerlink" title="Example"></a>Example</h3><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line">main() {</span><br><span class="line">    ClassMirror cm = reflectClass(ChildClass);</span><br><span class="line">    cm.instanceMembers.forEach((key, value) => <span class="built_in">print</span>(<span class="string">'<span class="subst">$key</span> >>> <span class="subst">$value<span class="string">'));</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">    </span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">    ClassMirror simpleCM = reflectClass(Simple);</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">    Simple simple = simpleCM.newInstance(Symbol.empty, ['</span></span>hey'</span>]) <span class="keyword">as</span> Simple;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Simple</span> </span>{</span><br><span class="line">  Simple(a) {</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">'A new Simple: <span class="subst">$a<span class="string">');</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  }</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">}</span></span></span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">SuperClass</span> </span>{</span><br><span class="line">  <span class="built_in">int</span> superField = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">final</span> <span class="built_in">int</span> superFinalField = <span class="number">1</span>;</span><br><span class="line">  <span class="built_in">int</span> <span class="keyword">get</span> superGetter => <span class="number">2</span>;</span><br><span class="line">  <span class="keyword">set</span> superSetter(x){ superField = x; }</span><br><span class="line">  <span class="built_in">int</span> superMethod(x) => <span class="number">4</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> <span class="built_in">int</span> superStaticField = <span class="number">5</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">final</span> <span class="built_in">int</span> superStaticFinalField = <span class="number">6</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">const</span> superStaticConstField = <span class="number">7</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="built_in">int</span> <span class="keyword">get</span> superStaticGetter => <span class="number">8</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">set</span> superStaticSetter(x) { }</span><br><span class="line">  <span class="keyword">static</span> <span class="built_in">int</span> superStaticMethod(x) => <span class="number">10</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ChildClass</span> <span class="keyword">extends</span> <span class="title">SuperClass</span> </span>{</span><br><span class="line">  <span class="built_in">int</span> aField = <span class="number">11</span>;</span><br><span class="line">  <span class="keyword">final</span> <span class="built_in">int</span> aFinalField = <span class="number">12</span>;</span><br><span class="line">  <span class="keyword">get</span> aGetter => <span class="number">13</span>;</span><br><span class="line">  <span class="keyword">set</span> aSetter(x) { aField = x; }</span><br><span class="line">  <span class="built_in">int</span> aMethod(x) => <span class="number">15</span>;</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> <span class="built_in">int</span> staticField = <span class="number">16</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">final</span> staticFinalField = <span class="number">17</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">const</span> staticConstField = <span class="number">18</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="built_in">int</span> <span class="keyword">get</span> staticGetter => <span class="number">19</span>;</span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">set</span> staticSetter(x) { staticField = x; }</span><br><span class="line">  <span class="keyword">static</span> <span class="built_in">int</span> staticMethod(x) => <span class="number">21</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出为</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Symbol(<span class="string">"=="</span>) >>> MethodMirror on <span class="string">'=='</span></span><br><span class="line">Symbol(<span class="string">"hashCode"</span>) >>> MethodMirror on <span class="string">'hashCode'</span></span><br><span class="line">Symbol(<span class="string">"toString"</span>) >>> MethodMirror on <span class="string">'toString'</span></span><br><span class="line">Symbol(<span class="string">"noSuchMethod"</span>) >>> MethodMirror on <span class="string">'noSuchMethod'</span></span><br><span class="line">Symbol(<span class="string">"runtimeType"</span>) >>> MethodMirror on <span class="string">'runtimeType'</span></span><br><span class="line">Symbol(<span class="string">"superField"</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"superField="</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"superFinalField"</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"superGetter"</span>) >>> MethodMirror on <span class="string">'superGetter'</span></span><br><span class="line">Symbol(<span class="string">"superSetter="</span>) >>> MethodMirror on <span class="string">'superSetter='</span></span><br><span class="line">Symbol(<span class="string">"superMethod"</span>) >>> MethodMirror on <span class="string">'superMethod'</span></span><br><span class="line">Symbol(<span class="string">"aField"</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"aField="</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"aFinalField"</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"aGetter"</span>) >>> MethodMirror on <span class="string">'aGetter'</span></span><br><span class="line">Symbol(<span class="string">"aSetter="</span>) >>> MethodMirror on <span class="string">'aSetter='</span></span><br><span class="line">Symbol(<span class="string">"aMethod"</span>) >>> MethodMirror on <span class="string">'aMethod'</span></span><br><span class="line">A new Simple: hey</span><br></pre></td></tr></tbody></table></figure><h3 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h3><p>在官方 API 页面可以看到所有的 Mirror 类型：<a href="https://api.dartlang.org/stable/2.3.0/dart-mirrors/dart-mirrors-library.html" target="_blank" rel="noopener">dart:mirrors library</a>。Mirror 的主要类型如下</p><ul><li><p>ClassMirror：Dart 类的反射类型</p></li><li><p>InstanceMirror：Dart 实例的反射类型</p></li><li><p>ClosureMirror： 闭包的反射类型</p></li><li><p>DeclarationMirror：类属性的反射类型</p></li><li><p>IsolateMirror：Isolate 的反射类型</p></li><li><p>MethodMirror：Dart 方法（包括函数、构造函数、getter/setter 函数）的反射类型</p></li></ul><p>通过<code>dart:mirrors</code>包内顶层函数<code>reflecClass</code> 获得类的“镜像”的实例，该实例的<code>instanceMembers</code>属性如下</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">Map</span><<span class="built_in">Symbol</span>, MethodMirror> <span class="keyword">get</span> instanceMembers;</span><br></pre></td></tr></tbody></table></figure><p>由控制台输出结果可以看到，对于普通字段（属性），除自身外还列出了以“=”结尾的 setter 字段，对于不提供 setter 的<code>final</code>字段则只出现一次。</p><p>使用<code>staticMembers</code>将列出所有的静态字段</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line">cm.staticMembers.forEach((key, value) => <span class="built_in">print</span>(<span class="string">'<span class="subst">$key</span> >>> <span class="subst">$value<span class="string">'));</span></span></span></span><br></pre></td></tr></tbody></table></figure><p>输出如下</p><figure class="highlight bash"><table><tbody><tr><td class="code"><pre><span class="line">Symbol(<span class="string">"staticField"</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"staticField="</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"staticFinalField"</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"staticConstField"</span>) >>> Instance of <span class="string">'_SyntheticAccessor'</span></span><br><span class="line">Symbol(<span class="string">"staticGetter"</span>) >>> MethodMirror on <span class="string">'staticGetter'</span></span><br><span class="line">Symbol(<span class="string">"staticSetter="</span>) >>> MethodMirror on <span class="string">'staticSetter='</span></span><br><span class="line">Symbol(<span class="string">"staticMethod"</span>) >>> MethodMirror on <span class="string">'staticMethod'</span></span><br></pre></td></tr></tbody></table></figure><p>可以发现父类静态成员没有出现在列表中，这是因为静态属性不会被继承、不能被<code>ChildClass</code>调用。</p><h3 id="Symbol"><a href="#Symbol" class="headerlink" title="Symbol"></a>Symbol</h3><p><code>Symbol</code>表示使用 Dart 的 mirror API 反射得到的实例类型，位于<code>dart:core</code>包</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">part</span> of dart.core;</span><br><span class="line"></span><br><span class="line"><span class="comment">/// Opaque name used by mirrors, invocations and [Function.apply].</span></span><br><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Symbol</span> </span>{</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">const</span> <span class="built_in">Symbol</span> unaryMinus = <span class="keyword">const</span> <span class="built_in">Symbol</span>(<span class="string">"unary-"</span>);</span><br><span class="line"></span><br><span class="line">  <span class="keyword">static</span> <span class="keyword">const</span> <span class="built_in">Symbol</span> empty = <span class="keyword">const</span> <span class="built_in">Symbol</span>(<span class="string">""</span>);</span><br><span class="line"></span><br><span class="line">  <span class="comment">//工厂构造方法</span></span><br><span class="line">  <span class="comment">//也可以直接通过 Symbol s = #name; 创建</span></span><br><span class="line">  <span class="keyword">const</span> <span class="keyword">factory</span> <span class="built_in">Symbol</span>(<span class="built_in">String</span> name) = internal.<span class="built_in">Symbol</span>;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">int</span> <span class="keyword">get</span> hashCode;</span><br><span class="line">  </span><br><span class="line">  <span class="built_in">bool</span> <span class="keyword">operator</span> ==(other);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="源码"><a href="#源码" class="headerlink" title="源码"></a>源码</h3><p>通过 ClassMirror 的源码，可以大概看出 Dart 语言关于反射的设计思想以及对外提供的 API</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">ClassMirror</span> <span class="keyword">implements</span> <span class="title">TypeMirror</span>, <span class="title">ObjectMirror</span> </span>{</span><br><span class="line"></span><br><span class="line">  ClassMirror <span class="keyword">get</span> superclass;<span class="comment">//父类 ， Object的父类为null</span></span><br><span class="line"></span><br><span class="line">  <span class="built_in">List</span><ClassMirror> <span class="keyword">get</span> superinterfaces;<span class="comment">//接口列表</span></span><br><span class="line"></span><br><span class="line">  <span class="built_in">bool</span> <span class="keyword">get</span> isAbstract;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">bool</span> <span class="keyword">get</span> isEnum;</span><br><span class="line"></span><br><span class="line">  <span class="built_in">Map</span><<span class="built_in">Symbol</span>, DeclarationMirror> <span class="keyword">get</span> declarations;<span class="comment">//不包含父类属性和方法</span></span><br><span class="line"></span><br><span class="line">  <span class="built_in">Map</span><<span class="built_in">Symbol</span>, MethodMirror> <span class="keyword">get</span> instanceMembers;<span class="comment">//实例属性</span></span><br><span class="line"></span><br><span class="line">  <span class="built_in">Map</span><<span class="built_in">Symbol</span>, MethodMirror> <span class="keyword">get</span> staticMembers;<span class="comment">//静态属性</span></span><br><span class="line"></span><br><span class="line">  <span class="comment">//如果S = A with B ,那么ClassMirror（S）.mixin 为 ClassMirror（B），否则返回本身</span></span><br><span class="line">  ClassMirror <span class="keyword">get</span> mixin;</span><br><span class="line">    </span><br><span class="line">  <span class="comment">/**</span></span><br><span class="line"><span class="comment">   * 调用构造方法</span></span><br><span class="line"><span class="comment">   * @param constructorName 构造方法名称（默认构造方法为空字符串，命名构造方法为其命名）</span></span><br><span class="line"><span class="comment">   * @param positionalArguments 参数列表</span></span><br><span class="line"><span class="comment">   */</span></span><br><span class="line">  InstanceMirror newInstance(<span class="built_in">Symbol</span> constructorName, <span class="built_in">List</span> positionalArguments,</span><br><span class="line">      [<span class="built_in">Map</span><<span class="built_in">Symbol</span>, <span class="keyword">dynamic</span>> namedArguments]);</span><br><span class="line"></span><br><span class="line">  <span class="built_in">bool</span> <span class="keyword">operator</span> ==(other);</span><br><span class="line"></span><br><span class="line">  <span class="built_in">bool</span> isSubclassOf(ClassMirror other);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="影响"><a href="#影响" class="headerlink" title="影响"></a>影响</h2><p>在 Java 中，当开发者多次（10w 次以上）访问、修改某一属性时，使用反射的成本会比正常访问高很多，同时会让<code>private</code>修饰符失去作用。在 Dart 中，反射的影响主要在于，编译器使用<code>tree shaking</code>的过程确定应用真正运行时使用的代码，以减少程序的大小。但是使用反射将使<code>tree shaking</code>失效，因为任何代码都有可能被使用，由此严重影响应用的启动时间和内存占用。</p><p>解决👆一问题的有效方法是，通过代码生成执行反射。为了“告知”编译器使用反射的代码和方式，开发者可以使用<code>dart:reflectable</code>库，通过特定元数据注解反射代码。<a href="https://github.com/dart-lang/reflectable" target="_blank" rel="noopener">reflectable</a></p><p>另一个影响在于最小化，其表示对下载到 Web 浏览器的源程序进行压缩的过程。在最小化过程中，源代码使用的名称在编译代码中被压缩成了短名称。这一过程会对反射带来不良影响，因为最小化之后，原来表示声明的名称的字符串，不再对应程序中的实际名称。</p><p>为了解决这一问题， Dart 反射使用 symbol 而非字符串作为 key，symbol 会被执行最小化的程序<code>minifier</code>识别并使用与标识符同样的压缩方式。这也是上面的输出中出现<code>Symbol(...)</code>的原因。开发者也可以通过 MirrorSystem 提供的<code>static String getName(Symbol symbol)</code>方法获得非最小化名称字符串。 </p><h2 id="小结"><a href="#小结" class="headerlink" title="小结"></a>小结</h2><p>目前来看 Dart 还不算一门“足够完善”的语言，比如反射机制的不完全、文档教程匮乏等等，相信随着 Flutter 的发展，这门语言的发展会更加地好。关于 Dart 反射的知识全部来自 Gilad Bracha 所著《Dart 编程语言》，不知道是不是翻译的问题，写得不够明晰，看得也是一头雾水。希望有朝一日，更加掌握 Dart 的反射机制，再写一篇《Dart 反射机制详解》的文章 😄。</p><p><img src="https://images-na.ssl-images-amazon.com/images/I/51r64LJDGuL._SX369_BO1,204,203,200_.jpg" alt></p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在 &lt;a href=&quot;https://febers.github.io/Java-反射详解/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Java 反射详解&lt;/a&gt; 一文中，我们知道&lt;code&gt;反射是一种计算机处理方式，是程序可以访问、检测和修改它本身状态或行为的一种能力&lt;/code&gt;。换一个角度说，反射可以细分为自省——程序在运行时决定自身结构的能力，以及自我修正——程序在运行时改变自身的能力。Dart 的反射基于 mirror 概念，它指的是反映其他对象的对象，并且目前只支持自省，不支持自我修改。&lt;/p&gt;
    
    </summary>
    
      <category term="Dart" scheme="http://yoursite.com/categories/Dart/"/>
    
    
      <category term="Dart" scheme="http://yoursite.com/tags/Dart/"/>
    
      <category term="反射" scheme="http://yoursite.com/tags/%E5%8F%8D%E5%B0%84/"/>
    
  </entry>
  
  <entry>
    <title>Markdown 语法详解</title>
    <link href="http://yoursite.com/Markdown-%E8%AF%AD%E6%B3%95%E8%AF%A6%E8%A7%A3/"/>
    <id>http://yoursite.com/Markdown-语法详解/</id>
    <published>2019-05-19T07:36:02.000Z</published>
    <updated>2019-06-07T04:26:22.000Z</updated>
    
    <content type="html"><![CDATA[<p>这篇文章本应该在搭建博客之后就发布，一开始觉得 Markdown  的语法足够简单，熟能生巧，无需花费篇幅去记录；近来无事，反省了一下自己的错误认识，除去一些高级用法，了解这门用途广泛的标记语言的由来与发展，回顾它的基础语法如何将排版变成一件充满乐趣的事，完全值得专门写一篇文章。</p><p><img src="/Markdown-语法详解/markdown.png" alt="logo"></p><a id="more"></a><h2 id="关于"><a href="#关于" class="headerlink" title="关于"></a>关于</h2><p>Markdown 是一门轻量级的标记语言，由美国工程师 John Gruber 于2004年创造。这门语言的目的是让人们<code>使用易于阅读、易于撰写的纯文字格式，并选择性地转换成有效的 XHTML（或是 HTML）</code>。</p><p>Markdown 所谓的<strong>易读</strong>并不是指排版之后呈现的结果易读，而是指原始格式下的文件依然拥有优秀的可读性，不会像阅读原始 HTML 代码一样，满眼都是尖括号（可以通过 右键浏览器页面 -> 查看源代码 体验）。Markdown 的<strong>易写</strong>则体现在其语法足够简单，学习曲线平缓，并且在写作中基本可以脱离鼠标操作。</p><p>Markdown 的轻量级是相对于 LaTeX 来说的，这种基于 Tex 的排版系统广泛运用在高质量书籍印刷和复杂公式论文中。不过使用 Markdown 仍然可以使用一些基本的数学公式，比如<code>$ E = mc^2 $</code>、<code>$ \int_0^xf(x)dx $</code>，单个<code>$</code>用于行内公式，<code>$$</code>用于单行公式</p><p>比如 $ E = mc^2 $、$ \int_0^xf(x)dx $ </p><p>这需要不同平台上的 Markdown 数学公式插件的支持，本博客使用<code>Hexo</code> + <code>Github Pages</code>搭建，可以通过安装 <a href="https://www.mathjax.org" target="_blank" rel="noopener">MathJax</a> 实现。</p><h3 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h3><p>跟早期的 HTML 类似，Markdown 在发展的过程中衍生出了不同的版本，它们的基本语法上相通，但是在诸如表格、锚点、时序图等实现上出现了不一致。在关于语法规范化的讨论中，作者 John Gruber 认为，<code>不同的网站（和人们）有不同的需求，没有一种语法可以让所有人满意</code>。</p><p>现今 Markdown 的主要分类如下</p><ul><li>CommonMark：由 Stack Exchange、Github、Reddit 等组织发起的标准化项目。一开始名称为<code>Standard Markdown</code>，由于遭到作者的反对，更名<code>CommonMark</code></li><li>GFM：Github Flavored Markdown，由 Github 于2017年发布，基于 CommonMark。相信很多开发者都是通过一份<code>README.md</code>文件认识 Markdown，这也是本博客采用的版本。</li><li>Markdown Extra：基于 PHP、Python 和 Rudy 中实现的 Markdown。</li></ul><h3 id="编辑器"><a href="#编辑器" class="headerlink" title="编辑器"></a>编辑器</h3><p>市面上优秀的 Markdown 编辑器层出不穷，这也有力推动了 Markdown 的发展。可以使用<code>Sublime Text</code>配合插件编辑写作，可以使用<code>Typora</code>等优秀的跨平台工具实现所见即所得，也可以通过 <a href="https://www.zybuluo.com" target="_blank" rel="noopener">Cmd Markdown </a>在线书写并导出、发布。</p><p>博主使用的是<code>Typora。</code></p><p><img src="/Markdown-语法详解/编辑器.png" alt="编辑器"></p><p>相关链接：<a href="https://sspai.com/post/32483" target="_blank" rel="noopener">码字必备：18 款优秀的 Markdown 写作工具 | 2015 年度盘点</a>、 <a href="https://www.zhihu.com/question/19637157" target="_blank" rel="noopener">用 Markdown 写作用什么文本编辑器？ - 知乎</a></p><h2 id="初级语法"><a href="#初级语法" class="headerlink" title="初级语法"></a>初级语法</h2><h3 id="标题"><a href="#标题" class="headerlink" title="标题"></a>标题</h3><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line"><span class="section"># 一级标题</span></span><br><span class="line"><span class="section">## 二级标题</span></span><br><span class="line"><span class="section">### 三级标题</span></span><br><span class="line"><span class="section">#### 四级标题</span></span><br><span class="line"><span class="section">##### 五级标题</span></span><br><span class="line"><span class="section">###### 六级标题</span></span><br></pre></td></tr></tbody></table></figure><hr><h3 id="三级标题"><a href="#三级标题" class="headerlink" title="三级标题"></a>三级标题</h3><h4 id="四级标题"><a href="#四级标题" class="headerlink" title="四级标题"></a>四级标题</h4><h5 id="五级标题"><a href="#五级标题" class="headerlink" title="五级标题"></a>五级标题</h5><h6 id="六级标题"><a href="#六级标题" class="headerlink" title="六级标题"></a>六级标题</h6><hr><p>另外在 GFM 中，任意 1-6 个 <strong>#</strong> 标注的标题都会被添加上同名的锚点链接，比如<code># First Title</code>会被标注成<code>[First Title](#first-title)</code>（注意小写转换），因此我们可以在文章的其他地方，使用标注之后的格式跳转到任何标题，比如<code>[跳转至引言](#引言)</code></p><h3 id="文字"><a href="#文字" class="headerlink" title="文字"></a>文字</h3><table><thead><tr><th>格式</th><th>效果</th></tr></thead><tbody><tr><td><code>*斜体1*</code></td><td><em>斜体1</em></td></tr><tr><td><code>_斜体2_</code></td><td><em>斜体2</em></td></tr><tr><td><code>**粗体1**</code></td><td><strong>粗体1</strong></td></tr><tr><td><code>__粗体2__</code></td><td><strong>粗体2</strong></td></tr><tr><td><code>~~删除线~~</code></td><td><del>删除线</del></td></tr><tr><td><code>***斜粗体1***</code></td><td><strong><em>斜粗体1</em></strong></td></tr><tr><td><code>___斜粗体2___</code></td><td><strong><em>斜粗体2</em></strong></td></tr><tr><td><code>***~~斜粗体删除线1~~***</code>、<code>~~***斜粗体删除线2***~~</code></td><td><strong><em><del>斜粗体删除线</del></em></strong></td></tr></tbody></table><h3 id="表情"><a href="#表情" class="headerlink" title="表情"></a>表情</h3><p>GFM 语法支持添加 emoji 表情，输入不同的符号码（两个冒号包围的字符）可以显示出不同的表情。比如<code>:stuck_out_tongue_winking_eye:</code>：<span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f61c.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f61c.png?v8">😜</span></p><p><span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f47b.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f47b.png?v8">👻</span> <span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f436.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f436.png?v8">🐶</span> <span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f4a9.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f4a9.png?v8">💩</span> <span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f525.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f525.png?v8">🔥</span> <span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f647.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f647.png?v8">🙇</span></p><p><span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f604.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f604.png?v8">😄</span> <span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f6a3.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f6a3.png?v8">🚣</span> <span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f349.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f349.png?v8">🍉</span> <span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f3ca.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f3ca.png?v8">🏊</span> <span class="github-emoji" style="color: transparent;background:no-repeat url(https://assets-cdn.github.com/images/icons/emoji/unicode/1f342.png?v8) center/contain" data-src="https://assets-cdn.github.com/images/icons/emoji/unicode/1f342.png?v8">🍂</span></p><p>可以在此找到不同表情对应的符号码：<a href="https://www.webfx.com/tools/emoji-cheat-sheet/" target="_blank" rel="noopener">Emoji cheat sheet for GitHub, Basecamp, Slack & more</a></p><p>Hexo 默认不支持 emoji 表情，可以通过安装 <a href="https://github.com/crimx/hexo-filter-github-emojis" target="_blank" rel="noopener">hexo-filter-github-emojis</a> 实现</p><h3 id="分割线"><a href="#分割线" class="headerlink" title="分割线"></a>分割线</h3><p>使用三个（或多个连续）的<code>-</code>、<code>*</code>、<code>-</code>实现分割线效果</p><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line">---</span><br><span class="line"><span class="emphasis">___</span></span><br><span class="line"><span class="strong">*****</span>*</span><br></pre></td></tr></tbody></table></figure><hr><hr><hr><h3 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h3><p>链接分为文字链接和图片链接</p><h4 id="文字链接"><a href="#文字链接" class="headerlink" title="文字链接"></a>文字链接</h4><p><code>[ReBe](https://febers.github.io "鼠标悬停显示")</code>：<a href="https://febers.github.io" title="鼠标悬停显示" target="_blank" rel="noopener">ReBe</a></p><p>支持使用标识符标志地址，将真正的URL地址放在文末，比如</p><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line">[<span class="string">Github</span>][<span class="symbol">Github URL</span>]</span><br><span class="line">[<span class="symbol">Github URL</span>]:<span class="link">https://github.com/Febers</span></span><br></pre></td></tr></tbody></table></figure><p>效果如下:</p><p><a href="https://github.com/Febers" target="_blank" rel="noopener">Github</a></p><h4 id="图片链接"><a href="#图片链接" class="headerlink" title="图片链接"></a>图片链接</h4><p>基本格式为<code>![title](url)</code>，其中<code>title</code>可省略，<code>![](https://camo.githubusercontent.com...)</code></p><p><img src="https://camo.githubusercontent.com/abf9d87ce112444bca1ddfffaa2063f02a2c26d0/68747470733a2f2f7261772e6769746875622e636f6d2f6164616d2d702f6d61726b646f776e2d686572652f6d61737465722f73746f72652d6173736574732f646f732d65717569732d4d44482e6a7067" alt></p><h3 id="列表"><a href="#列表" class="headerlink" title="列表"></a>列表</h3><h4 id="有序列表"><a href="#有序列表" class="headerlink" title="有序列表"></a>有序列表</h4><p>看起来并不明显</p><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line"><span class="bullet">1. </span>PHP是最好的语言？</span><br><span class="line"><span class="bullet">2. </span>PHP是最好的语言！</span><br></pre></td></tr></tbody></table></figure><ol><li><p>PHP是最好的语言？</p></li><li><p>PHP是最好的语言！</p></li></ol><h4 id="无序列表"><a href="#无序列表" class="headerlink" title="无序列表"></a>无序列表</h4><p>可以使用<code>-</code>、<code>*</code>、<code>+</code>开头接空格，但在多级列表中最好使用<code>-</code></p><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line"><span class="bullet">- </span>PHP是最好的语言？</span><br><span class="line"><span class="bullet">* </span>PHP是最好的语言。</span><br><span class="line"><span class="bullet">+ </span>PHP是最好的语言！</span><br><span class="line"><span class="code">- 毫无疑问</span></span><br><span class="line"><span class="code">- 众所周知</span></span><br></pre></td></tr></tbody></table></figure><ul><li>PHP是最好的语言？</li></ul><ul><li>PHP是最好的语言。</li></ul><ul><li>PHP是最好的语言！<ul><li>毫无疑问<ul><li>众所周知</li></ul></li></ul></li></ul><h4 id="复选框列表"><a href="#复选框列表" class="headerlink" title="复选框列表"></a>复选框列表</h4><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line"><span class="bullet">- </span>[x] 大一</span><br><span class="line"><span class="bullet">- </span>[x] 大二</span><br><span class="line"><span class="bullet">- </span>[ ] 大三</span><br><span class="line"><span class="bullet">- </span>[ ] 大四</span><br></pre></td></tr></tbody></table></figure><ul><li>[x] 大一</li><li>[x] 大二</li><li>[ ] 大三</li><li>[ ] 大四</li></ul><p>Hexo 默认的渲染引擎 Marked 不支持 TODO list，可以更换为 markdown-it，之后实现的效果如 Typora 预览</p><p><img src="/Markdown-语法详解/todo-list.png" alt="todo-list"></p><h3 id="引用与高亮"><a href="#引用与高亮" class="headerlink" title="引用与高亮"></a>引用与高亮</h3><h4 id="引用"><a href="#引用" class="headerlink" title="引用"></a>引用</h4><p>使用<code>></code>实现引用，多个<code>></code>实现引用层级</p><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line"><span class="quote">> PHP</span></span><br><span class="line">>> 是</span><br><span class="line">>>> 最好的</span><br><span class="line">>>>> 语言</span><br></pre></td></tr></tbody></table></figure><blockquote><p>PHP</p><blockquote><p>是</p><blockquote><p>最好的</p><blockquote><p>语言</p></blockquote></blockquote></blockquote></blockquote><p>一般用在引用原文内容中</p><blockquote><p>一语未了，只听后院中有人笑声，说：“我来迟了，不曾迎接远客！”黛玉纳罕道：“这些人个个皆敛声屏气，恭肃严整如此，这来者系谁，这样放诞无礼？”心下想时，只见一群媳妇丫鬟围拥着一个人从后房门进来。这个人打扮与众姑娘不同，彩绣辉煌，恍若神妃仙子：头上戴着金丝八宝攒珠髻，绾着朝阳五凤挂珠钗；项上戴着赤金盘螭璎珞圈，裙边系着豆绿宫绦，双衡比目玫瑰佩；身上穿着缕金百蝶穿花大红洋缎窄褃袄，外罩五彩刻丝石青银鼠褂；下着翡翠撒花洋绉裙。一双丹凤三角眼，两弯柳叶吊梢眉，身量苗条，体格风骚，粉面含春威不露，丹唇未起笑先闻。黛玉连忙起身接见。</p></blockquote><h4 id="居中"><a href="#居中" class="headerlink" title="居中"></a>居中</h4><p>Markdown 中标准引用是 <code>></code> 符号后面加上引用内容，可以嵌套，或者搭配其他 Markdown 语法结合一起使用。但样式未免单一，不够美观，可以直接使用 HTML 语言插入，如下，使用 <code>class="blockquote-center"</code></p><figure class="highlight html"><table><tbody><tr><td class="code"><pre><span class="line"><span class="tag"><<span class="name">blockquote</span> <span class="attr">class</span>=<span class="string">"blockquote-center"</span>></span>勇者愤怒，抽刃向更强者；怯者愤怒，却抽刃向更弱者。</span><br><span class="line">    </span><br><span class="line">    鲁迅<span class="tag"></<span class="name">blockquote</span>></span></span><br></pre></td></tr></tbody></table></figure><blockquote class="blockquote-center">勇者愤怒，抽刃向更强者；怯者愤怒，却抽刃向更弱者。<br><br>    鲁迅</blockquote><p>也可以使用标签的方式<code>centerquote</code>（或者其别名<code>cq</code>）</p><figure class="highlight js"><table><tbody><tr><td class="code"><pre><span class="line">{% centerquote %}盛年不重来，一日难再晨。及时当勉励，岁月不待人。</span><br><span class="line"></span><br><span class="line">陶渊明 {% endcenterquote %}</span><br></pre></td></tr></tbody></table></figure><p><em>该标签不会被当前主题识别，且造成部署 Hexo 时报错，在 NexT 主题上正常使用</em></p><h4 id="Note"><a href="#Note" class="headerlink" title="Note"></a>Note</h4><p>使用前端框架 Bootstrap Callout 的语法，Hexo 提供了对其的支持，Content 可以搭配 Markdown 语法</p><figure class="highlight javascript"><table><tbody><tr><td class="code"><pre><span class="line">{% note class_name %} Content (md partial supported) {% endnote %}</span><br><span class="line"></span><br><span class="line">{% note <span class="keyword">default</span> %} This is a <span class="keyword">default</span> note {% endnote %}</span><br><span class="line"></span><br><span class="line">{% note primary %} This is a  primary note {% endnote %}</span><br><span class="line"></span><br><span class="line">{% note success %} This is a success note {% endnote %}</span><br><span class="line"></span><br><span class="line">{% note info %} This is a info note {% endnote %}</span><br><span class="line"></span><br><span class="line">{% note warning %} This is a warning note {% endnote %}</span><br><span class="line"></span><br><span class="line">{% note danger %} This is a danger note {% endnote %}</span><br></pre></td></tr></tbody></table></figure><p>实现类似笔记的引用效果，其中<code>class_name</code>可以为</p><ul><li><code>default</code></li><li><code>primary</code></li><li><code>success</code></li><li><code>info</code></li><li><code>warning</code></li><li><code>danger</code></li></ul><p><em>该标签不会被当前主题识别，且造成部署 Hexo 时报错，在 NexT 主题上正常使用</em></p><h4 id="高亮"><a href="#高亮" class="headerlink" title="高亮"></a>高亮</h4><p>使用单个反引号实现单行文本高亮，三个反引号实现代码块高亮。可以在第一个三反引号后添加语言名称实现不同的语法高亮</p><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line">PHP是<span class="code">`最好的`</span>语言</span><br></pre></td></tr></tbody></table></figure><p>PHP是<code>最好的</code>语言</p><p><img src="/Markdown-语法详解/kotlin.png" alt></p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">fun</span> <span class="title">main</span><span class="params">(args: <span class="type">Array</span><<span class="type">String</span>>)</span></span> {</span><br><span class="line">    print(<span class="string">"hello"</span>)</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="高级进阶"><a href="#高级进阶" class="headerlink" title="高级进阶"></a>高级进阶</h2><p>在不同的 Markdown 版本中实现可能不同</p><h3 id="表格"><a href="#表格" class="headerlink" title="表格"></a>表格</h3><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line">| 序号 | 列名1 | 列名2 |</span><br><span class="line">| - | - | - |</span><br><span class="line">| 0 | 一一  | 一二  |</span><br><span class="line">| 1 | 二一  | 二二  |</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th>序号</th><th>列名1</th><th>列名2</th></tr></thead><tbody><tr><td>0</td><td>一一</td><td>一二</td></tr><tr><td>1</td><td>二一</td><td>二二</td></tr></tbody></table><p>在分隔行（第二行）中的<code>-</code>右边添加<code>:</code>，表格内容实现右对齐效果，两边都加则为居中对齐，默认为左对齐</p><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line">| 序号 | 列名1 | 列名2 |</span><br><span class="line">| :-: | :-: | :-: |</span><br><span class="line">| 0 | 一一  | 一二  |</span><br></pre></td></tr></tbody></table></figure><table><thead><tr><th style="text-align:center">序号</th><th style="text-align:center">列名1</th><th style="text-align:center">列名2</th></tr></thead><tbody><tr><td style="text-align:center">0</td><td style="text-align:center">一一</td><td style="text-align:center">一二</td></tr></tbody></table><h3 id="流程图"><a href="#流程图" class="headerlink" title="流程图"></a>流程图</h3><p>分为两部分，第一部分定义元素，第二部分定义元素走向。定义元素语法为<code>tag=>type: content:>url</code>，其中<code>tag</code>为元素名称，<code>type</code>为元素类型，有以下6种</p><table><thead><tr><th>type</th><th>含义</th></tr></thead><tbody><tr><td>start</td><td>开始</td></tr><tr><td>end</td><td>结束</td></tr><tr><td>operation</td><td>操作</td></tr><tr><td>subroutine</td><td>子程序</td></tr><tr><td>condition</td><td>条件</td></tr><tr><td>inputoutput</td><td>输入或输出</td></tr></tbody></table><p><code>content</code>为在流程图方框中显示的内容</p><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line">st=>start: 开始:>https://www.markdown-syntax.com</span><br><span class="line">io=>inputoutput: 输入或输出</span><br><span class="line">op=>operation: 操作</span><br><span class="line">cond=>condition: Yes or No?</span><br><span class="line">sub=>subroutine: 子程序</span><br><span class="line">e=>end: 结束</span><br><span class="line"></span><br><span class="line">st->io->op->cond</span><br><span class="line">cond(yes)->e</span><br><span class="line">cond(no)->sub->io</span><br></pre></td></tr></tbody></table></figure><div id="flowchart-0" class="flow-chart"></div><p>Hexo 原生并不支持流程图，需要安装<a href="https://github.com/bubkoo/hexo-filter-flowchart" target="_blank" rel="noopener">hexo-filter-flowchart</a></p><h3 id="时序图"><a href="#时序图" class="headerlink" title="时序图"></a>时序图</h3><p><code>title</code>为时序图标题，<code>participant</code>定义时序图对象，<code>note</code>定义时序图中的说明，有三种方位控制</p><ul><li>left of, 表示说明位于当前对象的左侧</li><li>right of, 表示说明位于当前对象的右侧</li><li>over, 表示说明覆盖在当前对象（们）上</li></ul><p>不同对象之间使用箭头控制指向</p><ul><li>->：实线实箭头</li><li>–>：虚线实箭头</li><li>->>：实线虚箭头</li><li>–>>：虚线虚箭头</li></ul><figure class="highlight markdown"><table><tbody><tr><td class="code"><pre><span class="line">title: 时序图标题</span><br><span class="line">participant 大一</span><br><span class="line">participant 大二</span><br><span class="line">participant 大三</span><br><span class="line"></span><br><span class="line">note left of 大一: 大一好好学习</span><br><span class="line">note over 大二: 大二课程很多</span><br><span class="line">note right of 大三: 大三面临毕业</span><br><span class="line"></span><br><span class="line">大一->大一:大一留级</span><br><span class="line">大一->大二:大一迟早要到大二</span><br><span class="line">大二-->大三:大二不一定能升大三</span><br><span class="line">大二->>大三:大二不一定能升大三</span><br><span class="line">大三-->>大一:大三也可能回炉重造</span><br></pre></td></tr></tbody></table></figure><div id="sequence-0"></div><p>Hexo 默认同样不支持时序图，使用 <a href="https://github.com/bubkoo/hexo-filter-sequence" target="_blank" rel="noopener">hexo-filter-sequence</a>。具体的做法参考 <a href="http://wewelove.github.io/fcoder/2017/09/06/markdown-sequence/" target="_blank" rel="noopener">为 Hexo 增加时序图解析功能</a></p><h3 id="字符转义"><a href="#字符转义" class="headerlink" title="字符转义"></a>字符转义</h3><p>由于 Markdown 最终会渲染成 HTML 页面，所以一些特殊字符需要遵循 HTML 的转义规则，转义成实体字符才能显示，传送门：<a href="http://www.w3chtml.com/html/character.html" target="_blank" rel="noopener">HTML 转义字符</a>。以下图表中为了显示空格的实体多添加了一个空格</p><table><thead><tr><th>显示结果</th><th>描述</th><th>实体名称</th><th>实体编号</th></tr></thead><tbody><tr><td> </td><td>空格</td><td>&n bsp;</td><td>&# 160;</td></tr><tr><td><</td><td>小于号</td><td>&l t;</td><td>&# 60;</td></tr><tr><td>></td><td>大于号</td><td>&g t;</td><td>&# 62;</td></tr><tr><td>&</td><td>和号</td><td>&a mp;</td><td>&# 38;</td></tr><tr><td>“</td><td>引号</td><td>&q uot;</td><td>&# 34;</td></tr><tr><td>‘</td><td>撇号</td><td>&a pos; (IE不支持)</td><td>&# 39;</td><td><script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.7/raphael.min.js"></script><script src="https://cdnjs.cloudflare.com/ajax/libs/flowchart/1.6.5/flowchart.min.js"></script><textarea id="flowchart-0-code" style="display: none">st=>start: 开始:>https://www.markdown-syntax.comio=>inputoutput: 输入或输出op=>operation: 操作cond=>condition: Yes or No?sub=>subroutine: 子程序e=>end: 结束st->io->op->condcond(yes)->econd(no)->sub->io</textarea><textarea id="flowchart-0-options" style="display: none">{"scale":1,"line-width":2,"line-length":50,"text-margin":10,"font-size":12}</textarea><script>  var code = document.getElementById("flowchart-0-code").value;  var options = JSON.parse(decodeURIComponent(document.getElementById("flowchart-0-options").value));  var diagram = flowchart.parse(code);  diagram.drawSVG("flowchart-0", options);</script><script src="https://cdn.bootcss.com/webfont/1.6.28/webfontloader.js"></script><script src="https://cdn.bootcss.com/raphael/2.2.8/raphael.min.js"></script><script src="https://cdn.bootcss.com/snap.svg/0.5.1/snap.svg-min.js"></script><script src="https://cdn.bootcss.com/underscore.js/1.9.1/underscore-min.js"></script><script src="https://cdn.bootcss.com/js-sequence-diagrams/1.0.6/sequence-diagram-min.js"></script><textarea id="sequence-0-code" style="display: none">title: 大学生活participant 大一participant 大二participant 大三note left of 大一: 大一好好学习note over 大二: 大二课程很多note right of 大三: 大三面临毕业大一->大一:大一惨遭留级大一->大二:大一迟早要到大二大二-->大三:大二不一定能升大三大二->>大三:大二不一定能升大三大三-->>大一:大三也可能回炉重造</textarea><textarea id="sequence-0-options" style="display: none">{"theme":"simple"}</textarea><script>  var code = document.getElementById("sequence-0-code").value;  var options = JSON.parse(decodeURIComponent(document.getElementById("sequence-0-options").value));  var diagram = Diagram.parse(code);  diagram.drawSVG("sequence-0", options);</script></td></tr></tbody></table><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;这篇文章本应该在搭建博客之后就发布，一开始觉得 Markdown  的语法足够简单，熟能生巧，无需花费篇幅去记录；近来无事，反省了一下自己的错误认识，除去一些高级用法，了解这门用途广泛的标记语言的由来与发展，回顾它的基础语法如何将排版变成一件充满乐趣的事，完全值得专门写一篇文章。&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;/Markdown-语法详解/markdown.png&quot; alt=&quot;logo&quot;&gt;&lt;/p&gt;
    
    </summary>
    
      <category term="Markdown" scheme="http://yoursite.com/categories/Markdown/"/>
    
    
      <category term="Markdown" scheme="http://yoursite.com/tags/Markdown/"/>
    
  </entry>
  
  <entry>
    <title>利用反射实现 DrawerLayout 全屏滑动</title>
    <link href="http://yoursite.com/%E5%88%A9%E7%94%A8%E5%8F%8D%E5%B0%84%E5%AE%9E%E7%8E%B0-DrawerLayout-%E5%85%A8%E5%B1%8F%E6%BB%91%E5%8A%A8/"/>
    <id>http://yoursite.com/利用反射实现-DrawerLayout-全屏滑动/</id>
    <published>2019-05-06T04:58:30.000Z</published>
    <updated>2019-06-02T05:04:54.000Z</updated>
    
    <content type="html"><![CDATA[<p>在一个项目中需要用到 DrawerLayout，但是其默认实现为边缘滑动打开侧滑界面，只能指定左边缘或者右边缘。想要实现全屏滑动，思路是通过反射的方式修改 DrawerLayout 的相应属性，涉及到枯燥的源码阅读。在完成全屏滑动之后，又发现其默认实现了长按弹出侧滑界面，在全屏滑动下，用户长按任何地方都会跳出侧滑菜单，而且还会出现留白问题。研究半天，还是利用反射的思路一并解决，特此记录。<a id="more"></a></p><h2 id="DrawerLayout-侧滑"><a href="#DrawerLayout-侧滑" class="headerlink" title="DrawerLayout 侧滑"></a>DrawerLayout 侧滑</h2><p>在 DrawerLayout 中定义了两个变量，分别对应 Gravity 为 Left 和 Right 的滑动情景，两者并无实质分别，本文只分析 Left 的情况。此外，DrawerLayout 包含三种状态，STATE_IDLE（已打开或已关闭），STATE_DRAGGING（正在拖动），STATE_SETTLING（执行打开或关闭的动画过程中）。</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ViewDragHelper mLeftDragger;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> ViewDragHelper mRightDragger;</span><br></pre></td></tr></tbody></table></figure><p> 构造函数对一些变量做了初始化</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line">mLeftCallback = <span class="keyword">new</span> ViewDragCallback(Gravity.LEFT);</span><br><span class="line"></span><br><span class="line">mLeftDragger = ViewDragHelper.create(<span class="keyword">this</span>, TOUCH_SLOP_SENSITIVITY, mLeftCallback);</span><br><span class="line">mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);</span><br><span class="line">mLeftDragger.setMinVelocity(minVel);</span><br><span class="line">mLeftCallback.setDragger(mLeftDragger);</span><br></pre></td></tr></tbody></table></figure><p>ViewDraghelper 是官方提供的专门为自定义 ViewGroup 处理拖拽的手势类。此处用到的构造方法为</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ViewDragHelper <span class="title">create</span><span class="params">(@NonNull ViewGroup forParent, <span class="keyword">float</span> sensitivity,</span></span></span><br><span class="line"><span class="function"><span class="params">            @NonNull Callback cb)</span></span></span><br></pre></td></tr></tbody></table></figure><p>DrawerLayout 中侧滑打开界面正是通过 ViewDragHelper 实现的，查看 DrawerLayout 的<code>onTouchEvent</code>方法</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">boolean</span> <span class="title">onTouchEvent</span><span class="params">(MotionEvent ev)</span> </span>{</span><br><span class="line">mLeftDragger.processTouchEvent(ev);</span><br><span class="line">mRightDragger.processTouchEvent(ev);</span><br><span class="line"></span><br><span class="line"><span class="keyword">final</span> <span class="keyword">int</span> action = ev.getAction();</span><br><span class="line"><span class="keyword">boolean</span> wantTouchEvents = <span class="keyword">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">switch</span> (action & MotionEvent.ACTION_MASK) {</span><br><span class="line">......</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">    <span class="keyword">return</span> wantTouchEvents;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>其明显调用了 ViewDragHelper 的<code>processTouchEvent</code>方法处理 Touch 事件</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">processTouchEvent</span><span class="params">(MotionEvent ev)</span> </span>{</span><br><span class="line">......</span><br><span class="line">    <span class="keyword">switch</span> (action) {</span><br><span class="line">        <span class="keyword">case</span> MotionEvent.ACTION_DOWN: {</span><br><span class="line">            <span class="keyword">final</span> <span class="keyword">float</span> x = ev.getX();</span><br><span class="line">            <span class="keyword">final</span> <span class="keyword">float</span> y = ev.getY();</span><br><span class="line">            <span class="keyword">final</span> <span class="keyword">int</span> pointerId = ev.getPointerId(<span class="number">0</span>);</span><br><span class="line">            <span class="comment">//找到当前触摸点的最顶层的子View,作为需要操作的View</span></span><br><span class="line">            <span class="keyword">final</span> View toCapture = findTopChildUnder((<span class="keyword">int</span>) x, (<span class="keyword">int</span>) y);</span><br><span class="line">            <span class="comment">//保存当前Touch点发生的初始状态</span></span><br><span class="line">            saveInitialMotion(x, y, pointerId);</span><br><span class="line">            <span class="comment">//这里是点在一个正在滑动的侧滑栏上，使侧滑栏的状态由正在滑动状态变为正在拖动状态</span></span><br><span class="line">            tryCaptureViewForDrag(toCapture, pointerId);</span><br><span class="line">            <span class="comment">//处理侧滑栏的触摸触发区域是否触摸，如果触摸则通知回调，在DrawerLayout中处理，执行一个侧滑微弹的操作，也就是稍微弹出一点，表示触发了侧滑操作</span></span><br><span class="line">            <span class="keyword">final</span> <span class="keyword">int</span> edgesTouched = mInitialEdgesTouched[pointerId];</span><br><span class="line">            <span class="keyword">if</span> ((edgesTouched & mTrackingEdges) != <span class="number">0</span>) {</span><br><span class="line">                mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId);</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">break</span>;</span><br><span class="line">        }</span><br><span class="line">    ......</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>重点在<code>mInitialEdgesTouched[pointerId]</code>，其为一个保存边缘滑动值的 int 数组。在<code>saveInitialMotion</code>方法中发现其赋值过程</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">saveInitialMotion</span><span class="params">(<span class="keyword">float</span> x, <span class="keyword">float</span> y, <span class="keyword">int</span> pointerId)</span> </span>{</span><br><span class="line">ensureMotionHistorySizeForId(pointerId);</span><br><span class="line">mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x;</span><br><span class="line">mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y;</span><br><span class="line">mInitialEdgesTouched[pointerId] = getEdgesTouched((<span class="keyword">int</span>) x, (<span class="keyword">int</span>) y);</span><br><span class="line">mPointersDown |= <span class="number">1</span> << pointerId;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>原来是调用了<code>getEdgesTouched</code>方法</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">int</span> <span class="title">getEdgesTouched</span><span class="params">(<span class="keyword">int</span> x, <span class="keyword">int</span> y)</span> </span>{</span><br><span class="line"><span class="keyword">int</span> result = <span class="number">0</span>;</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT;</span><br><span class="line"><span class="keyword">if</span> (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP;</span><br><span class="line"><span class="keyword">if</span> (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT;</span><br><span class="line"><span class="keyword">if</span> (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM;</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>可以看到，该方法将判断<code>x < mParentView.get*() + mEdgeSize</code>，然后将对应的 result 返回。<code>mEdgeSize</code>即为边缘滑动的临界值，其初始化值为</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">final</span> <span class="keyword">float</span> density = context.getResources().getDisplayMetrics().density;</span><br><span class="line">mEdgeSize = (<span class="keyword">int</span>) (EDGE_SIZE * density + <span class="number">0.5f</span>);</span><br></pre></td></tr></tbody></table></figure><p>因此，要让 DrawerLayout 支持全屏滑动打开侧滑菜单而不是边缘滑动，重点便是要修改该值，将其设为屏幕宽度。</p><p>具体的反射代码（kotlin）</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//获取 ViewDragHelper，更改 edgeSizeField</span></span><br><span class="line"><span class="keyword">val</span> leftDraggerField = drawerLayout.javaClass.getDeclaredField(<span class="string">"mLeftDragger"</span>)</span><br><span class="line">leftDraggerField.isAccessible = <span class="literal">true</span></span><br><span class="line"><span class="keyword">val</span> leftDragger = leftDraggerField.<span class="keyword">get</span>(drawerLayout) <span class="keyword">as</span> ViewDragHelper</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> edgeSizeField = leftDragger.javaClass.getDeclaredField(<span class="string">"mEdgeSize"</span>)</span><br><span class="line">edgeSizeField.isAccessible = <span class="literal">true</span></span><br><span class="line"><span class="keyword">val</span> edgeSize = edgeSizeField.getInt(leftDragger)</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> displaySize = Point()</span><br><span class="line">activity.windowManager.defaultDisplay.getSize(displaySize)</span><br><span class="line">edgeSizeField.setInt(leftDragger, displaySize.x)</span><br></pre></td></tr></tbody></table></figure><h2 id="DrawerLayout-长按弹出"><a href="#DrawerLayout-长按弹出" class="headerlink" title="DrawerLayout 长按弹出"></a>DrawerLayout 长按弹出</h2><p><a href="#引言">引言</a></p><p>在 DrawerLayout 中，用户在非侧滑界面的 mEdgeSize 范围内长按，侧滑界面将弹出。当我们修改 mEdgeSize 为屏幕宽度之后，用户所有的长按动作都将触发原来的弹出逻辑，而且触发范围为屏幕宽度，侧滑菜单将过度右移，造成左侧边缘有空白。</p><p>原来是 DrawerLayout 的私有内部类 ViewDragCallback 重写了<code>onEdgeTouched</code>方法</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">ViewDragCallback</span> <span class="keyword">extends</span> <span class="title">ViewDragHelper</span>.<span class="title">Callback</span></span></span><br></pre></td></tr></tbody></table></figure><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">onEdgeTouched</span><span class="params">(<span class="keyword">int</span> edgeFlags, <span class="keyword">int</span> pointerId)</span> </span>{</span><br><span class="line">postDelayed(mPeekRunnable, PEEK_DELAY);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>该方法会执行一个 mPeekRunnable，其为内部类的私有 Runnable 类型的属性，其<code>run</code>方法执行了<code>peekDrawer</code>方法</p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">peekDrawer</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">final</span> View toCapture;</span><br><span class="line"><span class="keyword">final</span> <span class="keyword">int</span> childLeft;</span><br><span class="line"><span class="keyword">final</span> <span class="keyword">int</span> peekDistance = mDragger.getEdgeSize();</span><br><span class="line"><span class="keyword">final</span> <span class="keyword">boolean</span> leftEdge = mAbsGravity == Gravity.LEFT;</span><br><span class="line"><span class="keyword">if</span> (leftEdge) {</span><br><span class="line">toCapture = findDrawerWithGravity(Gravity.LEFT);</span><br><span class="line">childLeft = (toCapture != <span class="keyword">null</span> ? -toCapture.getWidth() : <span class="number">0</span>) + peekDistance;</span><br><span class="line">} <span class="keyword">else</span> {</span><br><span class="line">toCapture = findDrawerWithGravity(Gravity.RIGHT);</span><br><span class="line">childLeft = getWidth() - peekDistance;</span><br><span class="line">}</span><br><span class="line"><span class="comment">// Only peek if it would mean making the drawer more visible and the drawer isn't locked</span></span><br><span class="line"><span class="keyword">if</span> (toCapture != <span class="keyword">null</span> && ((leftEdge && toCapture.getLeft() < childLeft)</span><br><span class="line">|| (!leftEdge && toCapture.getLeft() > childLeft))</span><br><span class="line">&& getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) {</span><br><span class="line"><span class="keyword">final</span> LayoutParams lp = (LayoutParams) toCapture.getLayoutParams();</span><br><span class="line">mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop());</span><br><span class="line">lp.isPeeking = <span class="keyword">true</span>;</span><br><span class="line">invalidate();</span><br><span class="line"></span><br><span class="line">closeOtherDrawer();</span><br><span class="line">cancelChildViewTouch();</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>注意<code>mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop())</code>就是长按屏幕时，侧滑菜单会自动滑出来的原因。</p><p>解决这个问题着实费了一番脑筋，因为 ViewDragCallback 为私有内部类，外部无法直接得到其引用。幸好观察之后发现其实现了 ViewDragHelper.Callback 接口，从而让我们可以利用多态的方式，获取其反射实例</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//获取 Layout 的 ViewDragCallBack 实例“mLeftCallback”</span></span><br><span class="line"><span class="comment">//更改其属性 mPeekRunnable</span></span><br><span class="line"><span class="keyword">val</span> leftCallbackField = drawerLayout.javaClass.getDeclaredField(<span class="string">"mLeftCallback"</span>)</span><br><span class="line">leftCallbackField.isAccessible = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line"><span class="comment">//因为无法直接访问私有内部类，所以该私有内部类实现的接口非常重要，通过多态的方式获取实例</span></span><br><span class="line"><span class="keyword">val</span> leftCallback = leftCallbackField.<span class="keyword">get</span>(drawerLayout) <span class="keyword">as</span> ViewDragHelper.Callback</span><br><span class="line"></span><br><span class="line"><span class="keyword">val</span> peekRunnableField = leftCallback.javaClass.getDeclaredField(<span class="string">"mPeekRunnable"</span>)</span><br><span class="line">peekRunnableField.isAccessible = <span class="literal">true</span></span><br><span class="line"><span class="keyword">val</span> nullRunnable = Runnable {  }</span><br><span class="line">peekRunnableField.<span class="keyword">set</span>(leftCallback, nullRunnable)</span><br></pre></td></tr></tbody></table></figure><p>完美解决问题！</p><p>最后便是构建一个工具类</p><figure class="highlight kotlin"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">object</span> DrawerLayoutHelper {</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 通过反射的方式将 DrawerLayout 的侧滑范围设为全屏</span></span><br><span class="line"><span class="comment">     * 该方法存在一个问题，在侧滑范围内长按，也会划出菜单</span></span><br><span class="line"><span class="comment">     * 通过查看 DrawerLayout 的源码分析，其内部类 ViewDragCallback</span></span><br><span class="line"><span class="comment">     * 重写了 onEdgeTouched 方法，然后调用一个 Runnable 属性的变量 “mPeekRunnable”</span></span><br><span class="line"><span class="comment">     * 该变量调用了 peekDraw 方法，实现了长按划出侧滑菜单的功能</span></span><br><span class="line"><span class="comment">     * 同样使用反射将该 Runnable 更改为空实现</span></span><br><span class="line"><span class="comment">     *</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> activity</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> drawerLayout</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> displayWidthPercentage</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">setDrawerLeftEdgeSize</span><span class="params">(activity: <span class="type">Activity</span>?,</span></span></span><br><span class="line"><span class="function"><span class="params">                              drawerLayout: <span class="type">DrawerLayout</span>?,</span></span></span><br><span class="line"><span class="function"><span class="params">                              displayWidthPercentage: <span class="type">Float</span>)</span></span> {</span><br><span class="line">        <span class="keyword">if</span> (activity == <span class="literal">null</span> || drawerLayout == <span class="literal">null</span>) <span class="keyword">return</span></span><br><span class="line">        <span class="keyword">try</span> {</span><br><span class="line">            <span class="comment">//获取 ViewDragHelper，更改其 edgeSizeField 为 displayWidthPercentage*屏幕大小</span></span><br><span class="line">            <span class="keyword">val</span> leftDraggerField = drawerLayout.javaClass.getDeclaredField(<span class="string">"mLeftDragger"</span>)</span><br><span class="line">            leftDraggerField.isAccessible = <span class="literal">true</span></span><br><span class="line">            <span class="keyword">val</span> leftDragger = leftDraggerField.<span class="keyword">get</span>(drawerLayout) <span class="keyword">as</span> ViewDragHelper</span><br><span class="line"></span><br><span class="line">            <span class="keyword">val</span> edgeSizeField = leftDragger.javaClass.getDeclaredField(<span class="string">"mEdgeSize"</span>)</span><br><span class="line">            edgeSizeField.isAccessible = <span class="literal">true</span></span><br><span class="line">            <span class="keyword">val</span> edgeSize = edgeSizeField.getInt(leftDragger)</span><br><span class="line"></span><br><span class="line">            <span class="keyword">val</span> displaySize = Point()</span><br><span class="line">            activity.windowManager.defaultDisplay.getSize(displaySize)</span><br><span class="line">            edgeSizeField.setInt(leftDragger, Math.max(edgeSize, (displaySize.x * displayWidthPercentage).toInt()))</span><br><span class="line"></span><br><span class="line">            <span class="comment">//获取 Layout 的 ViewDragCallBack 实例“mLeftCallback”</span></span><br><span class="line">            <span class="comment">//更改其属性 mPeekRunnable</span></span><br><span class="line">            <span class="keyword">val</span> leftCallbackField = drawerLayout.javaClass.getDeclaredField(<span class="string">"mLeftCallback"</span>)</span><br><span class="line">            leftCallbackField.isAccessible = <span class="literal">true</span></span><br><span class="line"></span><br><span class="line">            <span class="comment">//因为无法直接访问私有内部类，所以该私有内部类实现的接口非常重要，通过多态的方式获取实例</span></span><br><span class="line">            <span class="keyword">val</span> leftCallback = leftCallbackField.<span class="keyword">get</span>(drawerLayout) <span class="keyword">as</span> ViewDragHelper.Callback</span><br><span class="line"></span><br><span class="line">            <span class="keyword">val</span> peekRunnableField = leftCallback.javaClass.getDeclaredField(<span class="string">"mPeekRunnable"</span>)</span><br><span class="line">            peekRunnableField.isAccessible = <span class="literal">true</span></span><br><span class="line">            <span class="keyword">val</span> nullRunnable = Runnable {  }</span><br><span class="line">            peekRunnableField.<span class="keyword">set</span>(leftCallback, nullRunnable)</span><br><span class="line"></span><br><span class="line">        } <span class="keyword">catch</span> (e: Exception) {</span><br><span class="line">            e.printStackTrace()</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">fun</span> <span class="title">setDrawerLeftEdgeFullScreen</span><span class="params">(activity: <span class="type">Activity</span>?, drawerLayout: <span class="type">DrawerLayout</span>?)</span></span> {</span><br><span class="line">        setDrawerLeftEdgeSize(activity, drawerLayout, <span class="number">1.0f</span>)</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;在一个项目中需要用到 DrawerLayout，但是其默认实现为边缘滑动打开侧滑界面，只能指定左边缘或者右边缘。想要实现全屏滑动，思路是通过反射的方式修改 DrawerLayout 的相应属性，涉及到枯燥的源码阅读。在完成全屏滑动之后，又发现其默认实现了长按弹出侧滑界面，在全屏滑动下，用户长按任何地方都会跳出侧滑菜单，而且还会出现留白问题。研究半天，还是利用反射的思路一并解决，特此记录。&lt;/p&gt;
    
    </summary>
    
      <category term="Android" scheme="http://yoursite.com/categories/Android/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
      <category term="反射" scheme="http://yoursite.com/tags/%E5%8F%8D%E5%B0%84/"/>
    
  </entry>
  
  <entry>
    <title>Android 自定义 View 详解</title>
    <link href="http://yoursite.com/Android-%E8%87%AA%E5%AE%9A%E4%B9%89-View-%E8%AF%A6%E8%A7%A3/"/>
    <id>http://yoursite.com/Android-自定义-View-详解/</id>
    <published>2019-05-03T10:34:00.000Z</published>
    <updated>2019-06-02T04:54:56.000Z</updated>
    
    <content type="html"><![CDATA[<p>开发者可以通过自定义 View 实现炫酷的效果，其过程涉及到 View 的层次结构、事件分发机制和 View 的工作原理等技术细节。本文将串联个知识点，通过代码揭开自定义 View 的面纱。</p><a id="more"></a><h2 id="Canvas"><a href="#Canvas" class="headerlink" title="Canvas"></a>Canvas</h2><h2 id="Paint"><a href="#Paint" class="headerlink" title="Paint"></a>Paint</h2><h2 id="自定义-View"><a href="#自定义-View" class="headerlink" title="自定义 View"></a>自定义 View</h2><h2 id="自定义-ViewGroup"><a href="#自定义-ViewGroup" class="headerlink" title="自定义 ViewGroup"></a>自定义 ViewGroup</h2><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;开发者可以通过自定义 View 实现炫酷的效果，其过程涉及到 View 的层次结构、事件分发机制和 View 的工作原理等技术细节。本文将串联个知识点，通过代码揭开自定义 View 的面纱。&lt;/p&gt;
    
    </summary>
    
      <category term="Android" scheme="http://yoursite.com/categories/Android/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
      <category term="View" scheme="http://yoursite.com/tags/View/"/>
    
  </entry>
  
  <entry>
    <title>Dart 异步编程</title>
    <link href="http://yoursite.com/Dart-%E5%BC%82%E6%AD%A5%E7%BC%96%E7%A8%8B/"/>
    <id>http://yoursite.com/Dart-异步编程/</id>
    <published>2019-04-30T00:21:26.000Z</published>
    <updated>2019-06-02T04:55:30.000Z</updated>
    
    <content type="html"><![CDATA[<p>Dart 属于单线程编程语言，在进行 I/O 操作或者其他耗时操作的时候，程序会进入阻塞状态。异步是 Dart 并发方案的基础。</p><a id="more"></a><h2 id="事件循环"><a href="#事件循环" class="headerlink" title="事件循环"></a>事件循环</h2><p>作为一个事件驱动语言，Dart 同样拥有事件循环（Event Loop，类似于 Android 中的Looper/Handler）。Dart 有两个队列，一个是微任务队列（MicroTask Queue），一个是事件队列（Event Queue）</p><ul><li>微任务队列包含 Dart 内部的微任务，主要通过<code>scheduleMicrotask</code>调度</li><li>事件队列包含外部事件，如 I/O、Timer、绘制事件等</li></ul><p><img src="/Dart-异步编程/事件循环.png.jpg" alt></p><p>从上图可以看出，Dart 处理事件循环的逻辑</p><ul><li>首先处理所有微任务队列里的微任务</li><li>处理完所有微任务之后，处理事件队列里的一个事件</li><li>回到微任务队列继续循环</li></ul><p>对于微任务队列，一次性全部处理，对于事件队列，一次只处理一个。</p><h2 id="微任务和事件"><a href="#微任务和事件" class="headerlink" title="微任务和事件"></a>微任务和事件</h2><h3 id="微任务"><a href="#微任务" class="headerlink" title="微任务"></a>微任务</h3><p><code>dart:async</code>定义了一个顶级函数<code>scheduleMicrotask</code>，使用其让代码以微任务的方式异步执行</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">import</span> <span class="string">'dart:async'</span>;<span class="comment">//下文不再显式导入</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'开始'</span>);</span><br><span class="line">  scheduleMicrotask(() {</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">'这是一个微任务'</span>);</span><br><span class="line">  });</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">"结束"</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出</p><blockquote><p>开始<br>结束<br>这是一个微任务</p></blockquote><h3 id="事件"><a href="#事件" class="headerlink" title="事件"></a>事件</h3><p>使用<code>Timer.run(callback)</code>让代码以事件的方式异步执行</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'开始'</span>);</span><br><span class="line"></span><br><span class="line">  Timer.run(() {</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">'这是一个事件'</span>);</span><br><span class="line">  });</span><br><span class="line"></span><br><span class="line">  scheduleMicrotask((){ <span class="built_in">print</span>(<span class="string">'这是微任务0'</span>); });</span><br><span class="line">  scheduleMicrotask((){ <span class="built_in">print</span>(<span class="string">'这是微任务1'</span>); });</span><br><span class="line">  scheduleMicrotask((){ <span class="built_in">print</span>(<span class="string">'这是微任务2'</span>); });</span><br><span class="line">  </span><br><span class="line">  <span class="built_in">print</span>(<span class="string">"结束"</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出</p><blockquote><p>开始<br>结束<br>这是微任务0<br>这是微任务1<br>这是微任务2<br>这是一个事件</p></blockquote><p>同时可以看出和 Java 使用<code>new Thread（Runnable r）</code>不同，在 Dart 中，微任务的执行顺序是有序的。</p><p>考虑下面的代码，会输出<code>这是一个事件</code>吗？</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line">Timer.run(() {</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'这是一个事件'</span>);</span><br><span class="line">});</span><br><span class="line"></span><br><span class="line">foo() {</span><br><span class="line">  scheduleMicrotask(foo);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">foo();</span><br></pre></td></tr></tbody></table></figure><p>根据上面 Dart 处理事件循环的逻辑图，<code>Timer.run</code>永远不会被执行，因为<code>scheduleMicrotask</code>永远在执行。</p><p>仅仅使用回调函数实现异步很容易陷入“回调地狱（Callback hell）”，为此 Dart 引入了<code>Future</code></p><h2 id="Future"><a href="#Future" class="headerlink" title="Future"></a>Future</h2><p>Future 封装了一系列静态函数完成异步操作，其内部通过<code>scheduleMicrotask</code>和<code>Timer</code>实现。此外还有一个<code>then</code>方法，接收一个名为<code>onValue</code>的闭包作为参数，该闭包在 Future 成功完成时被调用</p><table><thead><tr><th>函数</th><th>用途</th></tr></thead><tbody><tr><td>Future(FutureOr<t> computation())</t></td><td>创建事件任务</td></tr><tr><td>microtask(FutureOr<t> computation())</t></td><td>创建microtask任务</td></tr><tr><td>sync(FutureOr<t> computation())</t></td><td>创建同步任务</td></tr><tr><td>delayed(Duration duration, [FutureOr<t> computation()])</t></td><td>创建延迟任务</td></tr></tbody></table><p>通过代码理解</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'开始'</span>);</span><br><span class="line"></span><br><span class="line">  Timer.run(() => <span class="built_in">print</span>(<span class="string">'这是一个事件'</span>));</span><br><span class="line"></span><br><span class="line">  scheduleMicrotask((){<span class="built_in">print</span>(<span class="string">'这是微任务0'</span>);});</span><br><span class="line">  scheduleMicrotask((){<span class="built_in">print</span>(<span class="string">'这是微任务1'</span>);});</span><br><span class="line">  scheduleMicrotask((){<span class="built_in">print</span>(<span class="string">'这是微任务2'</span>);});</span><br><span class="line"></span><br><span class="line">  <span class="built_in">print</span>(<span class="string">"结束"</span>);</span><br><span class="line"></span><br><span class="line">  Future(() => <span class="built_in">print</span>(<span class="string">'普通Future，通过Timer实现'</span>));</span><br><span class="line"></span><br><span class="line">  Future.delayed(<span class="keyword">const</span> <span class="built_in">Duration</span>(seconds: <span class="number">2</span>), () => <span class="built_in">print</span>(<span class="string">'延迟Future，通过Timer实现'</span>));</span><br><span class="line"></span><br><span class="line">  Future.microtask(() => <span class="built_in">print</span>(<span class="string">'Future创建微任务，通过scheduleMicrotask实现'</span>));</span><br><span class="line"></span><br><span class="line">  Future.<span class="keyword">sync</span>(() => <span class="built_in">print</span>(<span class="string">'同步Future，执行同步代码'</span>))</span><br><span class="line">      .then((a) => <span class="built_in">print</span>(<span class="string">'then中的代码0'</span>))</span><br><span class="line">      .then((b) => <span class="built_in">print</span>(<span class="string">'then中的代码1'</span>))</span><br><span class="line">      .then((c) { <span class="keyword">throw</span> <span class="string">'抛出then中的错误'</span>; })</span><br><span class="line">      .catchError((error) => <span class="built_in">print</span>(<span class="string">'捕获Error <span class="subst">$error<span class="string">'))</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">      .whenComplete(() {print('</span></span>then任务完成'</span>);});</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>输出结果</p><blockquote><p>开始<br>结束<br>同步Future，执行同步代码<br>这是微任务0<br>这是微任务1<br>这是微任务2<br>Future创建微任务，通过scheduleMicrotask实现<br>then中的代码0<br>then中的代码1<br>捕获Error 抛出then中的错误<br>then任务完成<br>这是一个事件<br>普通Future，通过Timer实现</p><p>//延迟2s</p><p>延迟Future，通过Timer实现</p></blockquote><hr><p>在<code>dart:async</code>中，除了 Future，还有 Completer，用来将具体的 Future 流程控制权交给开发者</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> completer = Completer();</span><br><span class="line"><span class="keyword">var</span> future = completer.future;</span><br><span class="line">future.then((d) => <span class="string">'返回的字符串'</span>)</span><br><span class="line">    .then((e) => <span class="built_in">print</span>(<span class="string">'获得Completer中的future <span class="subst">$e<span class="string">'));</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">completer.complete((e) => print('</span></span>设为完成状态'</span>));</span><br></pre></td></tr></tbody></table></figure><p>控制台输出</p><blockquote><p>获得Completer中的future 返回的字符串</p></blockquote><p>虽然 Future 缓解了回调地狱的问题，但如果串太多的<code>then</code>代码，可读性仍然会非常差，特别是各种 Future 嵌套的时候。与 JavaScript 类似，Dart 引入了<code>async/await</code>。</p><h2 id="async-和-await"><a href="#async-和-await" class="headerlink" title="async 和 await"></a>async 和 await</h2><p>async 关键字修饰的函数与传统函数并无区别，只是将返回值类型使用 Future 进行了封装。</p><p>通过代码具体理解</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> main() <span class="keyword">async</span> {</span><br><span class="line">  getInt().then((i) => <span class="built_in">print</span>(<span class="string">'getInt: <span class="subst">$i<span class="string">'));</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  getString().then((s) => print('</span></span>getString: <span class="subst">$s<span class="string">'));</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  print('</span></span>main'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">getInt() <span class="keyword">async</span> => <span class="number">2333</span>;</span><br><span class="line">getString() <span class="keyword">async</span> => <span class="string">'hello'</span>;</span><br></pre></td></tr></tbody></table></figure><p>控制台输出</p><blockquote><p>main<br>getInt: 2333<br>getString: hello</p></blockquote><p>可以看到，调用<code>async</code>方法的代码转换成了异步任务。要想使之变成同步顺序，使用<code>await</code>关键字。不过需要注意的是，该关键字必须要在<code>async</code>函数中使用</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> main() <span class="keyword">async</span> {</span><br><span class="line">  <span class="keyword">var</span> i = <span class="keyword">await</span> getInt();</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'getInt: <span class="subst">$i<span class="string">');</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  </span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  var s = await getString();</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  print('</span></span>getString: <span class="subst">$s<span class="string">');</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  </span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  print('</span></span>main'</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出</p><blockquote><p>getInt: 2333<br>getString: hello<br>main</p></blockquote><p>继续下面的例子</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'main 0'</span>);</span><br><span class="line">  foo();</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'main 1'</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">foo() <span class="keyword">async</span> {</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'Foo'</span>);</span><br><span class="line">  <span class="keyword">var</span> s = <span class="keyword">await</span> bar();</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'from bar: <span class="subst">$s<span class="string">');</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">}</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string"></span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">bar() {</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  print('</span></span>Bar'</span>);</span><br><span class="line">  <span class="keyword">return</span> <span class="string">'hello'</span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出为</p><blockquote><p>main 0<br>Foo<br>Bar<br>main 1<br>from bar: hello</p></blockquote><p>也就是说，在<code>foo</code>中，除了第一行代码以及<code>bar()</code>这一函数调用之外的其他代码均为异步执行。当使用<code>await</code>的时候，其右边会马上返回一个 Future 对象，下面的代码则会以<code>then</code>的形式运行。</p><p>上面的代码转换成 Future 风格</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line">foo() {</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'Foo'</span>);</span><br><span class="line">  <span class="keyword">return</span> Future.<span class="keyword">sync</span>(bar).then((s) => <span class="built_in">print</span>(<span class="string">'from bar: <span class="subst">$s<span class="string">'));</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">}</span></span></span></span><br></pre></td></tr></tbody></table></figure><h2 id="Generator"><a href="#Generator" class="headerlink" title="Generator"></a>Generator</h2><h3 id="stream"><a href="#stream" class="headerlink" title="stream"></a>stream</h3><p>stream 是 Dart 中一个长度不确定的值列表，可以是有限的或者无限的，重要的是我们不知道 stream 何时结束或已经结束。随时间改变的鼠标位置、所有素数的列表或者网络上的视频流，都可以看做一个 stream。</p><p>可以通过为 stream 注册一个或多个回调函数的方式，对其进行订阅监听。</p><h3 id="yield"><a href="#yield" class="headerlink" title="yield"></a>yield</h3><p>yield 语句被用于生成器函数内，目的是给生成的集合添加新的结果。yield 语句总是使它的表达式被求值，通常情况下，求值结果会被追加到外层生成器所关联的集合中。如果生成器是同步的，则关联的集合是一个 iterable；如果是异步的，则关联的集合是一个 stream。</p><p>此外，yield 也会因外层的生成器是否同步产生不同的行为：同步时 yield 会暂停外层生成器，直至调用<code>moveNext</code>且返回值为 true，异步时生成器的执行会继续。</p><h3 id="异步"><a href="#异步" class="headerlink" title="异步"></a>异步</h3><p>一个函数体标记有<code>async*</code>修饰符的函数，将作为 stream 的生成函数。下面的函数生成一个包含自然数序列的 stream</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">get</span> naturals <span class="keyword">async</span>* {</span><br><span class="line">  <span class="built_in">int</span> k = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span> (k < <span class="number">3</span>) {</span><br><span class="line">    <span class="keyword">yield</span> <span class="keyword">await</span> k++;</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() <span class="keyword">async</span> {</span><br><span class="line">  <span class="keyword">await</span> <span class="keyword">for</span> (<span class="keyword">var</span> i <span class="keyword">in</span> naturals) {</span><br><span class="line">    <span class="built_in">print</span>(<span class="string">'get a natural <span class="subst">$i<span class="string">');</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  }</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">}</span></span></span></span><br></pre></td></tr></tbody></table></figure><p>运行<code>main</code>函数，控制台将输出</p><blockquote><p>get a natural 0<br>get a natural 1<br>get a natural 2</p></blockquote><p>当 naturals 被调用时，立即返回一个新的 stream，一旦 stream 被监听，函数体将运行，以便生成值来填充 stream。每一次迭代执行一次 yield 语句，k 将自增（由于 await 的存在，函数会有短暂停止），然后函数将继续执行并使用新的 k 值，该值将被 yield 追加到 stream 中。</p><h3 id="同步"><a href="#同步" class="headerlink" title="同步"></a>同步</h3><p>上述函数的同步形式</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">Iterable</span> naturalsTo(n) <span class="keyword">sync</span>* {</span><br><span class="line">  <span class="built_in">int</span> k = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span>(k < n) {</span><br><span class="line">    <span class="keyword">yield</span> k++;</span><br><span class="line">  }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><hr><p>通过一个混合编程的例子来体会两者的区别</p><figure class="highlight javascript"><table><tbody><tr><td class="code"><pre><span class="line">Iterable nSync(n) sync* {</span><br><span class="line">  int k = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span> (k < n)  {</span><br><span class="line">    print(<span class="string">'sync before k++ and k is $k'</span>);</span><br><span class="line">    <span class="keyword">yield</span> k++;</span><br><span class="line">    print(<span class="string">'sync after k++ and k is $k'</span>);</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">Stream nAsync(n) <span class="keyword">async</span>* {</span><br><span class="line">  int k = <span class="number">0</span>;</span><br><span class="line">  <span class="keyword">while</span> (k < n)  {</span><br><span class="line">    print(<span class="string">'async before k++ and k is $k'</span>);</span><br><span class="line">    <span class="keyword">yield</span> <span class="keyword">await</span> k++;</span><br><span class="line">    print(<span class="string">'async after k++ and k is $k'</span>);</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">  nAsync(<span class="number">2</span>).last;</span><br><span class="line">  nSync(<span class="number">2</span>).last;</span><br><span class="line">  print(<span class="string">'main'</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出为</p><blockquote><p>sync before k++ and k is 0<br>sync after k++ and k is 1<br>sync before k++ and k is 1<br>sync after k++ and k is 2<br>main<br>async before k++ and k is 0<br>async after k++ and k is 1<br>async before k++ and k is 1<br>async after k++ and k is 2</p></blockquote><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;Dart 属于单线程编程语言，在进行 I/O 操作或者其他耗时操作的时候，程序会进入阻塞状态。异步是 Dart 并发方案的基础。&lt;/p&gt;
    
    </summary>
    
      <category term="Dart" scheme="http://yoursite.com/categories/Dart/"/>
    
    
      <category term="Dart" scheme="http://yoursite.com/tags/Dart/"/>
    
      <category term="异步" scheme="http://yoursite.com/tags/%E5%BC%82%E6%AD%A5/"/>
    
  </entry>
  
  <entry>
    <title>《红楼梦》读书笔记</title>
    <link href="http://yoursite.com/%E3%80%8A%E7%BA%A2%E6%A5%BC%E6%A2%A6%E3%80%8B%E8%AF%BB%E4%B9%A6%E7%AC%94%E8%AE%B0/"/>
    <id>http://yoursite.com/《红楼梦》读书笔记/</id>
    <published>2019-04-24T03:35:40.000Z</published>
    <updated>2019-06-02T13:45:52.000Z</updated>
    
    <content type="html"><![CDATA[<p>完整认真地读完一遍《红楼梦》，应该是长久以来的念想。小学时 ，四叔寄给我一套中华书局出版的四大名著，从那时开始与之结缘。中学至今，书读得不少，但终究没有一次认真细致地拜读这一古典著作。这篇读书笔记，主要是记录章节概括和精彩之处，同时也是对自己的督促，不过更新周期可能会异常的漫长。</p><a id="more"></a><h2 id="第一回-甄士隐梦幻识通灵-贾雨村风尘怀闺秀"><a href="#第一回-甄士隐梦幻识通灵-贾雨村风尘怀闺秀" class="headerlink" title="第一回 甄士隐梦幻识通灵　贾雨村风尘怀闺秀"></a>第一回 甄士隐梦幻识通灵　贾雨村风尘怀闺秀</h2><h3 id="缘起"><a href="#缘起" class="headerlink" title="缘起"></a>缘起</h3><blockquote><p>曾历过一番梦幻之后，故将真事隐去，而借通灵说此《石头记》一书也，故曰“甄士隐”云云。</p><p>“….我虽不学无文，又何妨用假语村言敷演出来？亦可使闺阁昭传。复可破一时之闷，醒同人之目，不亦宜乎？”故曰“贾雨村”云云。</p></blockquote><p>女娲补天所用三万六千五百零一块石头剩一块通灵，被一道人镌刻文字于其上，名《石头记》</p><blockquote><p>上面述着堕落之乡、投胎之处，以及家庭琐事、闺阁闲情、诗词谜语，倒还全备</p></blockquote><p>后所记录被空空道人所抄写，改《情僧录》，东鲁孔梅溪题《风月宝鉴》，曹雪芹于悼红轩批阅增删、分目录章节，题曰《金陵十二衩》，此为《石头记》缘起。曹雪芹题诗</p><blockquote><p>满纸荒唐言，一把辛酸泪。</p><p>都云作者痴，水解其中味！</p></blockquote><h3 id="开篇"><a href="#开篇" class="headerlink" title="开篇"></a>开篇</h3><p>姑苏城仁青巷中有一乡宦甄士隐，梦中忽逢一僧一道了结灵石公案。原来那石头各地游玩，入警幻仙子麾下，任赤霞宫神瑛侍者。因以甘露灌溉西方灵河绛珠草，结下姻缘。绛珠草为感谢雨露之惠，欲“还泪”解缘。一僧一道携一众风流冤家下凡。三人行至“太虚幻境”，两边有一对联</p><blockquote><p>假作真时真亦假</p><p>无为有处有还无</p></blockquote><p>甄士隐与附近葫芦庙穷儒贾雨村交好。中秋佳节两人畅饮，甄士隐资助贾雨村进京赶考后，独女英莲走失，家院因火灾烧成瓦砾，投靠岳父封肃遭受白眼非议。一日碰一跛足道人，听道人语有感</p><blockquote><p>金满箱，银满箱，转眼乞丐人皆谤。正叹他人命不长，那知自己归来丧？训有方，保不定日后做强梁。择膏粱，谁承望流落在烟花巷！因嫌纱帽小，致使锁枷扛。昨怜破袄寒，今嫌紫蟒长：乱烘烘你方唱罢我登场，反认他乡是故乡。故荒唐，到头来都是“为他人作嫁衣裳”。</p></blockquote><p>便于道人飘飘而去。</p><h2 id="第二回-贾夫人仙逝扬州城-冷子兴演说荣国府"><a href="#第二回-贾夫人仙逝扬州城-冷子兴演说荣国府" class="headerlink" title="第二回 贾夫人仙逝扬州城　冷子兴演说荣国府"></a>第二回 贾夫人仙逝扬州城　冷子兴演说荣国府</h2><p>贾雨村受甄士隐赠银、进京赶考，中了进士，升任本县太爷，遣人至封府答谢甄士隐，同时将当年在甄士隐家中回首相顾的丫头娇杏娶作二房。然其恃才贪腐，不到一年便被革职，于是前往各地游览。</p><p>游至维扬时，入当朝盐政官林如海府中给其幼女林黛玉作西席。林如海年过五十，黛玉为嫡妻贾氏所生，聪明俊秀但体弱多病，其母一病身亡，需守丧尽礼，旧病复发。贾雨村便闲居无聊。</p><p>一日贾雨村郊外村肆中偶遇旧友冷子兴，二人聊起荣、宁两府。宁国公与荣国公是一母同胞兄弟。宁国公贾演生有两子，长子贾代化同有两子，稍长之子贾敷早死，次子贾敬袭官，然而一味好道终日炼丹。贾敬有一子贾珍，生下一子名贾蓉。荣国公贾源的长子贾代善在其去世后袭官，娶金陵世家史侯的小姐为妻，生有两子，长子贾赦袭官，次子贾政任员外郎。贾政夫人王氏生下二胎女儿十几年后，竟又生一嘴衔五彩晶玉的男孩，其名贾宝玉。</p><p>贾宝玉抓阄时只取脂粉钗环，引得贾政不喜。如今十来岁，淘气异常，聪明乖觉，自言</p><blockquote><p>女儿是水做的骨肉，男子是泥做的骨肉。我见了女儿便清爽，建了男子便觉浊臭逼人。</p><p>必得两个女儿陪着我读书，我方能认得字，心上也明白，不然我心里自己糊涂</p><p>这女儿两个字极尊贵极清静的，比那瑞兽珍禽、奇花异草更觉稀罕尊贵</p></blockquote><p>贾雨村道天下运隆，清明灵秀之气比比皆是，与那残忍乖邪之气搏击掀发。邪气附于人，上则不能为仁人君子、下亦不能为大凶大恶，正所谓“成则公侯败则贼”。复感慨其必不能守祖父基业、从师友规劝。</p><p>贾政长女因正月初一出生，故名元春、贤孝才德，入宫作女史官；二女名迎春，三女名探春，四女名惜春，俱在祖母身边读书。林如海其妻、林黛玉其母名贾敏，原来是贾赦、贾政胞妹。贾赦有一子稍长于宝玉，名贾琏，娶了贾政夫人王氏的侄女，模样极标志、言谈又爽利、心机又极深细，正是王熙凤。</p><h2 id="第三回-托内兄如海荐西宾-接外孙贾母惜孤女"><a href="#第三回-托内兄如海荐西宾-接外孙贾母惜孤女" class="headerlink" title="第三回 托内兄如海荐西宾　接外孙贾母惜孤女"></a>第三回 托内兄如海荐西宾　接外孙贾母惜孤女</h2><p>都中（一说南京）要起复旧员，林如海为贾雨村写荐书投身贾政，恰巧林黛玉祖母念其无人依傍，派遣船只接其入贾府，两人便一同进京。</p><p>黛玉弃舟登岸后，有轿子伺候，行半日见一大门，有匾<code>敕造宁国府</code>，往西不远一大门，正是<code>荣国府</code>。贾母一见黛玉，搂入怀中，“心肝儿肉”叫着大哭，旁人无不下泪。不一时有三位姑娘前来接客，正是迎、探、惜春三姐妹</p><blockquote><p>第一个肌肤微丰，身材合中，腮凝新荔，鼻腻鹅脂，温柔沉默，观之可亲。第二个削肩细腰，长挑身材，鸭蛋脸儿，俊眼修眉，顾盼神飞，文彩精华，见之忘俗。第三个身量未足，形容尚小。其钗环裙袄，三人皆是一样的妆束。</p></blockquote><p>黛玉谈起自己的病患，言其三岁时有一癞头和尚要其出家，父母不从，和尚便说</p><blockquote><p>既舍不得他，但只怕他的病一生也不能好的！若要好时，除非从此以后总不许见哭声…方可平安了此一生</p></blockquote><p>此时后院中有笑语声，“我来迟了，没得迎接远客！”黛玉思忖旁人皆敛声屏气，唯独此人放诞无礼。来者正是王熙凤</p><blockquote><p>彩绣辉煌，恍若神妃仙子。头上戴着金丝八宝攒珠髻，绾着朝阳五凤挂珠钗，项上戴着赤金盘螭璎珞圈，裙边系着豆绿宫绦，双衡比目玫瑰佩，身上穿着缕金百蝶穿花大红洋缎窄褃袄，外罩五彩刻丝石青银鼠褂，下着翡翠撒花洋绉裙。一双丹凤三角眼，两弯柳叶吊梢眉，身量苗条，体格风骚，粉面含春威不露，丹唇未启笑先闻</p></blockquote><p>相互打过招呼，王熙凤笑道，“天下竟有这样标致人儿！我今日才算看见了！”又交代好贾府上下事务。 </p><p>黛玉在丫鬟嬷嬷带领下拜见王夫人，用膳饮茶，正巧宝玉归来由寺庙还原归来</p><blockquote><p>头上戴着束发嵌宝紫金冠，齐眉勒着二龙抢珠金抹额，穿一件二色金百蝶穿花大红箭袖，束着五彩丝攒花结长穗宫绦，外罩石青起花八团倭锻排穗褂，登着青缎粉底小朝靴。面若中秋之月，色如春晓之花，鬓若刀裁，眉如墨画，面如桃瓣，目若秋波。虽怒时而若笑，即瞋视而有情。项上金螭璎珞，又有一根五色丝绦，系着一块美玉</p></blockquote><p>黛玉心惊，“好生奇怪，倒像在那里见过的，何等眼熟！”宝玉见过其母，又换一身行头</p><blockquote><p>身上穿着银红撒花半旧大袄，仍旧带着项圈、宝玉、寄名锁、护身<br>符等物，下面半露松绿撒花绫裤，锦边弹墨袜，厚底大红鞋。越显得面如傅粉，唇<br>若施脂，转盼多情，语言若笑。天然一段风韵，全在眉梢；平生万种情思，悉堆眼<br>角</p></blockquote><p>有词曰</p><blockquote><p>无故寻愁觅恨，有时似傻如狂。纵然生得好皮囊，腹内原来草莽。</p><p>潦倒不通庶务，愚顽怕读文章。行为偏僻性乖张，那管世人诽谤！</p><p>富贵不知乐业，贫穷难耐凄凉。可怜辜负好时光，于国于家无望。</p><p>天下无能第一，古今不肖无双。寄言纨绔与膏粱：莫效此儿形状！</p></blockquote><p>宝玉见了黛玉，细看形容，与众各别</p><blockquote><p>两弯似蹙非蹙罥烟眉，一双似喜非喜含情目。态生两靥之愁，娇袭一身之病。泪光点点，娇喘微微。闲静时如姣花照水，行动处似弱柳扶风。心较比干多一窍，病如西子胜三分。</p></blockquote><p>看罢笑道：“这个妹妹我曾见过的。”又问黛玉是否有玉，黛玉答无，发起痴狂，便欲摔玉。</p><p>黛玉安置于贾府碧纱橱，身旁有奶娘王嬷嬷、随身丫头雪雁、鹦哥，外又有宝玉乳母李嬷嬷、大丫鬟袭人服侍。袭人本名花珍珠，贾母溺爱宝玉，便令心地纯良、恪尽职守的袭人侍奉宝玉。宝玉曾见诗云“花气袭人”，便命其改名。</p><p>次日贾府收到金陵书信，说是城中所居薛家姨母之子薛蟠，倚财仗势，闹出了人命。</p><h2 id="第四回-薄命女偏逢薄命郎-葫芦僧乱判葫芦案"><a href="#第四回-薄命女偏逢薄命郎-葫芦僧乱判葫芦案" class="headerlink" title="第四回 薄命女偏逢薄命郎　葫芦僧乱判葫芦案"></a>第四回 薄命女偏逢薄命郎　葫芦僧乱判葫芦案</h2><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;完整认真地读完一遍《红楼梦》，应该是长久以来的念想。小学时 ，四叔寄给我一套中华书局出版的四大名著，从那时开始与之结缘。中学至今，书读得不少，但终究没有一次认真细致地拜读这一古典著作。这篇读书笔记，主要是记录章节概括和精彩之处，同时也是对自己的督促，不过更新周期可能会异常的漫长。&lt;/p&gt;
    
    </summary>
    
      <category term="文学" scheme="http://yoursite.com/categories/%E6%96%87%E5%AD%A6/"/>
    
    
      <category term="文学" scheme="http://yoursite.com/tags/%E6%96%87%E5%AD%A6/"/>
    
      <category term="笔记" scheme="http://yoursite.com/tags/%E7%AC%94%E8%AE%B0/"/>
    
      <category term="红楼梦" scheme="http://yoursite.com/tags/%E7%BA%A2%E6%A5%BC%E6%A2%A6/"/>
    
  </entry>
  
  <entry>
    <title>Dart 面向对象</title>
    <link href="http://yoursite.com/Dart-%E9%9D%A2%E5%90%91%E5%AF%B9%E8%B1%A1/"/>
    <id>http://yoursite.com/Dart-面向对象/</id>
    <published>2019-04-23T01:10:53.000Z</published>
    <updated>2019-06-02T04:55:22.000Z</updated>
    
    <content type="html"><![CDATA[<p>作为一门面向对象的语言，Dart 在很多方面跟 Java 都很相似。Dart 中所有对象都是类的实例，所有类都属于 Object 的子类，类的继承则使用 Mixin 机制。</p><a id="more"></a><h2 id="定义"><a href="#定义" class="headerlink" title="定义"></a>定义</h2><p>使用 class 关键字定义一个类。与 Java 类似，如果没有显示地定义构造函数，会默认一个无参构造函数。使用 new 关键字和构造函数来创建对象。</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line">  <span class="built_in">num</span> x;</span><br><span class="line">  <span class="built_in">num</span> y;</span><br><span class="line">  <span class="built_in">num</span> z;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">  <span class="keyword">var</span> point = <span class="keyword">new</span> Point();</span><br><span class="line">  <span class="built_in">print</span>(point.hasCode);<span class="comment">//未定义父类的时候，默认继承自Object</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="构造函数"><a href="#构造函数" class="headerlink" title="构造函数"></a>构造函数</h3><p>可以在构造函数的参数前加 this 关键字直接赋值</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Point</span> </span>{</span><br><span class="line">    <span class="built_in">num</span> x;</span><br><span class="line">    <span class="built_in">num</span> y;</span><br><span class="line">    <span class="built_in">num</span> z;</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//第一个值传递给this.x，第二个值传递给this.y</span></span><br><span class="line">    Point(<span class="keyword">this</span>.x, <span class="keyword">this</span>.y, z) {</span><br><span class="line">            <span class="keyword">this</span>.z = z;</span><br><span class="line">    }</span><br><span class="line">    </span><br><span class="line">    <span class="comment">//命名构造函数，格式为Class.name(var param)</span></span><br><span class="line">    Point.fromeList(<span class="keyword">var</span> list): </span><br><span class="line">            x = list[<span class="number">0</span>], y = list[<span class="number">1</span>], z = list[<span class="number">2</span>]{<span class="comment">//使用冒号初始化变量</span></span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    <span class="comment">//当然，上面也可以简写为：</span></span><br><span class="line">    <span class="comment">//Point.fromeList(var list):this(list[0], list[1], list[2]);</span></span><br><span class="line"></span><br><span class="line">     <span class="built_in">String</span> toString() => <span class="string">'x:<span class="subst">$x</span>  y:<span class="subst">$y</span>  z:<span class="subst">$z<span class="string">';</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">}</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string"></span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">//调用父类的构造方法</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">class ColorPoint extends Point {</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  String color;</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string"></span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  ColorPoint.fromXYZAndColor(num x, num y, num z, String color)</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">      : super.fromXYZ(x, y, z) {</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">    this.color = color;</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">    print('</span></span>ColorPoint'</span>);</span><br><span class="line">  }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">    <span class="keyword">var</span> p1 = <span class="keyword">new</span> Point(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>);</span><br><span class="line">    <span class="keyword">var</span> p2 = <span class="keyword">new</span> Point.fromeList([<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>]);</span><br><span class="line">    <span class="built_in">print</span>(p1);<span class="comment">//默认调用toString()函数</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>需要创建不可变对象的话，可以在构造函数前使用<code>const</code>关键字定义编译时常量对象</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">ImmutablePoint</span> </span>{</span><br><span class="line">    <span class="keyword">final</span> <span class="built_in">num</span> x;</span><br><span class="line">    <span class="keyword">final</span> <span class="built_in">num</span> y;</span><br><span class="line">    <span class="keyword">const</span> ImmutablePoint(<span class="keyword">this</span>.x, <span class="keyword">this</span>.y); <span class="comment">// 常量构造函数</span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> ImmutablePoint origin = <span class="keyword">const</span> ImmutablePoint(<span class="number">0</span>, <span class="number">0</span>); <span class="comment">// 创建一个常量对象不能用new，要用const</span></span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="工厂构造函数"><a href="#工厂构造函数" class="headerlink" title="工厂构造函数"></a>工厂构造函数</h3><p>Dart 中一种获取单例对象的方式，使用工厂模式来定义构造函数。对于调用者来说，仍然使用 new 关键字来获取对象，具体的实现细节对外隐藏。</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Logger</span> </span>{ </span><br><span class="line"><span class="keyword">final</span> <span class="built_in">String</span> name; </span><br><span class="line">    <span class="built_in">bool</span> mute = <span class="keyword">false</span>; </span><br><span class="line">    </span><br><span class="line">    <span class="comment">// 变量前加下划线表示私有属性 </span></span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="built_in">Map</span><<span class="built_in">String</span>, Logger> _cache = <<span class="built_in">String</span>, Logger>{}; </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">factory</span> Logger(<span class="built_in">String</span> name) { </span><br><span class="line">        <span class="keyword">if</span> (_cache.containsKey(name)) { </span><br><span class="line">            <span class="keyword">return</span> _cache[name]; </span><br><span class="line">        } <span class="keyword">else</span> { </span><br><span class="line">            <span class="keyword">final</span> logger = <span class="keyword">new</span> Logger._internal(name); </span><br><span class="line">            _cache[name] = logger; </span><br><span class="line">            <span class="keyword">return</span> logger; </span><br><span class="line">        } </span><br><span class="line">    }</span><br><span class="line"></span><br><span class="line">    Logger._internal(<span class="keyword">this</span>.name); </span><br><span class="line">    </span><br><span class="line">    <span class="keyword">void</span> log(<span class="built_in">String</span> msg) { </span><br><span class="line">        <span class="keyword">if</span> (!mute) { </span><br><span class="line">            <span class="built_in">print</span>(<span class="string">'<span class="subst">$name</span>: <span class="subst">$msg<span class="string">'); </span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">        } </span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">    } </span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">} </span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string"></span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">var logger = new Logger('</span></span>UI'</span>); </span><br><span class="line">logger.log(<span class="string">'Button clicked'</span>);</span><br></pre></td></tr></tbody></table></figure><h3 id="Getter-Setter"><a href="#Getter-Setter" class="headerlink" title="Getter / Setter"></a>Getter / Setter</h3><p>用来读写对象的属性，每个属性都对应一个隐式的 Getter 和 Setter，通过<code>obj.x</code>调用。类似于 Kotlin，可以使用<code>get</code>、<code>set</code>关键字拓展相应的功能。如果属性为<code>final</code>或者<code>const</code>，则只有对外的 Getter。</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Rectangle</span> </span>{</span><br><span class="line">    <span class="built_in">num</span> left;</span><br><span class="line">    <span class="built_in">num</span> top;</span><br><span class="line">    <span class="built_in">num</span> width;</span><br><span class="line">    <span class="built_in">num</span> height;</span><br><span class="line"></span><br><span class="line">    Rectangle(<span class="keyword">this</span>.left, <span class="keyword">this</span>.top, <span class="keyword">this</span>.width, <span class="keyword">this</span>.height);</span><br><span class="line">    </span><br><span class="line">    <span class="comment">// right 和 bottom 两个属性的计算方法</span></span><br><span class="line">    <span class="built_in">num</span> <span class="keyword">get</span> right => left + width;</span><br><span class="line">    <span class="keyword">set</span> right(<span class="built_in">num</span> value) => left = value - width;</span><br><span class="line">    </span><br><span class="line">    <span class="built_in">num</span> <span class="keyword">get</span> bottom => top + height;</span><br><span class="line">    <span class="keyword">set</span> bottom(<span class="built_in">num</span> value) => top = value - height;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">    <span class="keyword">var</span> rect = <span class="keyword">new</span> Rectangle(<span class="number">3</span>, <span class="number">4</span>, <span class="number">20</span>, <span class="number">15</span>);</span><br><span class="line">    <span class="keyword">assert</span>(rect.left == <span class="number">3</span>);</span><br><span class="line">    </span><br><span class="line">    rect.right = <span class="number">12</span>;</span><br><span class="line">    <span class="keyword">assert</span>(rect.left == <span class="number">-8</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h3 id="类方法"><a href="#类方法" class="headerlink" title="类方法"></a>类方法</h3><p>实例方法跟 Java 类似，抽象方法有所不同，不需要使用<code>abstract</code>显示定义，只需要在方法签名后用<code>;</code>来代替方法体即表示其为一抽象方法，Dart 中非抽象类也可以定义抽象方法。</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">Bird</span> </span>{</span><br><span class="line">  <span class="keyword">void</span> fly();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">Sparrow</span> <span class="keyword">extends</span> <span class="title">Bird</span> </span>{</span><br><span class="line">  <span class="keyword">void</span> fly() {</span><br><span class="line"></span><br><span class="line">  }</span><br><span class="line">    </span><br><span class="line">  <span class="keyword">void</span> sleep();</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><h2 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h2><p>在 Dart 中没有“接口”这一概念，类分为抽象类和非抽象类，唯一的区别是后者不可直接实例化。Dart 中仍然使用了<code>implements</code>和<code>extends</code>关键字，不过两者有所不同</p><ul><li>implements 代表实现，子类无法访问父类的参数，可以实现多个类</li><li>extends 代表继承，可继承父类的非私有变量，使用单继承机制</li></ul><p>在构造函数体前使用 <code>super</code>关键字调用父类构造函数，使用<code>@override</code>来注解复写父类的方法。</p><h3 id="Mixin-继承机制"><a href="#Mixin-继承机制" class="headerlink" title="Mixin 继承机制"></a>Mixin 继承机制</h3><h4 id="继承歧义"><a href="#继承歧义" class="headerlink" title="继承歧义"></a>继承歧义</h4><p>在了解该机制之前先认识“继承歧义”，也叫“菱形问题”。当两个类 B 和 C 继承自 A，D类继承自 B 和 C 时将产生歧义。当 A 中有一个方法在 B 和 C 中已经重写，而 D 没有 重写，那么 D 继承的方法的版本是 B 还是 C？</p><p><img src="/Dart-面向对象/继承歧义.png" alt="继承歧义"></p><p>不同的编程语言有不同的方法处理该问题</p><table><thead><tr><th>语言</th><th style="text-align:center">解决方案</th></tr></thead><tbody><tr><td>C++</td><td style="text-align:center">需要显式地声明要使用的特性是从哪个父类调用的(如：<code>Worker::Human.Age</code>)。C++不支持显式的重复继承，因为无法限定要使用哪个父类</td></tr><tr><td>Java 8</td><td style="text-align:center">Java 8 在接口上引入默认方法。如果<code>A、B、C</code>是接口，<code>B、C</code>可以为<code>A</code>的抽象方法提供不同的实现，从而导致<code>菱形问题</code>。<code>D</code>类必须重新实现该方法，否则发生编译错误。（Java 8 之前不支持多重继承、没有默认方法）</td></tr></tbody></table><p>Dart 使用 Mixin 机制解决该方法，或者写作“mix-in（混入）”更容易理解。</p><h3 id="Mixin"><a href="#Mixin" class="headerlink" title="Mixin"></a>Mixin</h3><p>在 Java8 之前，由于单继承机制以及接口没有默认方法，避免了继承歧义，而 Dart 虽然也使用了单继承机制，但是没有<code>interface</code>这一概念 —— 实际上，Dart 中的每一个类都可以被<code>implements</code> —— 所以使用了基于线性逻辑的<code>Mixin</code>解决该问题。</p><p><code>Mixin</code>即为混入：<code>Mixins are a way of reusing a class’s code in multiple class hierarchies</code>。</p><p>通过一个例子理解</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>{</span><br><span class="line">  <span class="keyword">var</span> s = <span class="string">'A'</span>;</span><br><span class="line">  <span class="keyword">get</span>() => <span class="string">'A'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">B</span> </span>{</span><br><span class="line">  <span class="keyword">var</span> s = <span class="string">'B'</span>;</span><br><span class="line">  <span class="keyword">get</span>() => <span class="string">'B'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">P</span> </span>{</span><br><span class="line">  <span class="keyword">var</span> s = <span class="string">'P'</span>;</span><br><span class="line">  <span class="keyword">get</span>() => <span class="string">'P'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AB</span> <span class="keyword">extends</span> <span class="title">P</span> <span class="title">with</span> <span class="title">A</span>, <span class="title">B</span> </span>{</span><br><span class="line">  <span class="keyword">var</span> s = <span class="string">'AB'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BA</span> <span class="keyword">extends</span> <span class="title">P</span> <span class="title">with</span> <span class="title">B</span>, <span class="title">A</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">  AB ab = AB();</span><br><span class="line">  <span class="built_in">print</span>(ab.<span class="keyword">get</span>());</span><br><span class="line">  <span class="built_in">print</span>(ab.s);</span><br><span class="line"></span><br><span class="line">  BA ba = BA();</span><br><span class="line">  <span class="built_in">print</span>(ba.<span class="keyword">get</span>());</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>控制台输出为：</p><blockquote><p>B<br>AB<br>A</p></blockquote><p>这是因为下面的代码</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">AB</span> <span class="keyword">extends</span> <span class="title">P</span> <span class="title">with</span> <span class="title">A</span>, <span class="title">B</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">BA</span> <span class="keyword">extends</span> <span class="title">P</span> <span class="title">with</span> <span class="title">B</span>, <span class="title">A</span> </span>{}</span><br></pre></td></tr></tbody></table></figure><p>相当于</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PA</span> = <span class="title">P</span> <span class="title">with</span> <span class="title">A</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">PAB</span> = <span class="title">PA</span> <span class="title">with</span> <span class="title">B</span>;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">AB</span> <span class="keyword">extends</span> <span class="title">PAB</span> </span>{}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">PB</span> = <span class="title">P</span> <span class="title">with</span> <span class="title">B</span>;</span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">PBA</span> = <span class="title">PB</span> <span class="title">with</span> <span class="title">A</span>;</span></span><br><span class="line"><span class="class"></span></span><br><span class="line"><span class="class"><span class="title">class</span> <span class="title">BA</span> <span class="keyword">extends</span> <span class="title">PBA</span> </span>{}</span><br></pre></td></tr></tbody></table></figure><p>继承图如下</p><p><img src="/Dart-面向对象/ABP继承图.png" alt="ABP继承图"></p><p>有意思的是，当我们将上面的代码中的<code>with</code>换成<code>implements</code>时，输出的结果将为</p><blockquote><p>P<br>AB<br>P</p></blockquote><h3 id="extends、with、implements"><a href="#extends、with、implements" class="headerlink" title="extends、with、implements"></a>extends、with、implements</h3><p>在 Dart 中，类声明必须严格按照 extends -> with -> implements 的顺序</p><ul><li><p>extends 的用法类似于 Java，唯一的不同在于子类可以完全访问父类的属性和函数，因为在 Dart 中并没有私有、公有的概念，下划线<code>_</code>的仅仅是一种约定。</p></li><li><p>除了上面的内容，<code>with</code>还可以与之搭配关键字<code>on</code>，表示要进行 mixin 的类必须先 “implements” 被 mixin 的类声明中 on 关键字后面的类，否则编译失败</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">D</span> </span>{</span><br><span class="line">  <span class="keyword">void</span> fromD();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">mixin C on D {</span><br><span class="line">  fromC() => <span class="string">'C'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">/*</span></span><br><span class="line"><span class="comment">  下面的代码将编译失败，因为 F 要 mixin C 必须先“implements” D</span></span><br><span class="line"><span class="comment">  但是 implements 关键字又必须在 with 的后面，所以只能定义一个新的类 E</span></span><br><span class="line"><span class="comment">  使 E implements D，F 再 extends E，才能 mixin C</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">F</span> <span class="title">with</span> <span class="title">C</span> </span>{ }</span><br></pre></td></tr></tbody></table></figure><p>正确的做法</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">E</span> <span class="keyword">implements</span> <span class="title">D</span> </span>{</span><br><span class="line">  <span class="meta">@override</span></span><br><span class="line">  <span class="keyword">void</span> fromD() => <span class="string">'E'</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">F</span> <span class="keyword">extends</span> <span class="title">E</span> <span class="title">with</span> <span class="title">C</span> </span>{ }</span><br></pre></td></tr></tbody></table></figure></li><li><p>Dart 中每个类都是一个隐式地接口。<code>implements</code>一个类之后，必须<code>override</code>所有的方法和成员变量</p></li></ul><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;作为一门面向对象的语言，Dart 在很多方面跟 Java 都很相似。Dart 中所有对象都是类的实例，所有类都属于 Object 的子类，类的继承则使用 Mixin 机制。&lt;/p&gt;
    
    </summary>
    
      <category term="Dart" scheme="http://yoursite.com/categories/Dart/"/>
    
    
      <category term="Dart" scheme="http://yoursite.com/tags/Dart/"/>
    
  </entry>
  
  <entry>
    <title>Dart 基础入门</title>
    <link href="http://yoursite.com/Dart%20%E5%9F%BA%E7%A1%80%E5%85%A5%E9%97%A8/"/>
    <id>http://yoursite.com/Dart 基础入门/</id>
    <published>2019-04-22T06:27:18.000Z</published>
    <updated>2019-06-02T04:55:08.000Z</updated>
    
    <content type="html"><![CDATA[<p>终于开始 Flutter 的具体学习，一切从 Dart 语言开始。</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">void main() {</span><br><span class="line">    print('hello world');</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><a id="more"></a><h2 id="环境搭建"><a href="#环境搭建" class="headerlink" title="环境搭建"></a>环境搭建</h2><p>Dart 的 SDK 下载可能需要梯子。作者使用的开发环境是 Intellij IDEA，首先下载 Dart 的 Plugin，重启 IDEA 之后创建一个 Dart Project，当然首先需要确认 SDK 的路径。</p><p><img src="/Dart 基础入门/idea_create.png" alt></p><p>创建之后的文件窗口如下，右键 DartDemo 文件夹，新建一个 dart 文件，和 C/C++ 类似，Dart 语言以文件中的<code>main</code>函数作为运行的入口。在<code>run</code>之前需要<code>edit configuration</code>，很简单，只要指定对应的文件即可。</p><p><img src="/Dart 基础入门/idea_category.png" alt></p><h2 id="声明变量"><a href="#声明变量" class="headerlink" title="声明变量"></a>声明变量</h2><p>Dart 是一门完全面向对象的语言，包括基本数据类型、函数都是对象，继承自 Object。声明一个对象可以初始化，否则其值为<code>null</code>。可以使用具体的类型声明，也可以使用<code>var</code>、<code>dynamic</code>、<code>const</code>、<code>final</code>等关键字</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">void main() {</span><br><span class="line">  var i = 0;</span><br><span class="line">  var d = 2.0;</span><br><span class="line">  var s = 'hello';</span><br><span class="line">  var b = true;</span><br><span class="line">  var l = [1, 2, 3];</span><br><span class="line">  var m = {0: 'a', 1: 'b'};</span><br><span class="line">  </span><br><span class="line">  print(main is Function);  //true</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>通过查看官方库的<code>core</code>包，可以大概看出其结构</p><p><img src="/Dart 基础入门/dart_core.png" alt="dart_core"></p><h2 id="数据类型"><a href="#数据类型" class="headerlink" title="数据类型"></a>数据类型</h2><h3 id="Numbers"><a href="#Numbers" class="headerlink" title="Numbers"></a>Numbers</h3><p>包括 int 和 double，分别代表整形和浮点型。</p><p>int 的数值范围不超过2的64位，具体与平台有关，一般为 -2^53 to 2^53。double 则属于64位的双精度浮点型数据。</p><h3 id="String"><a href="#String" class="headerlink" title="String"></a>String</h3><p>在 Dart 中，可以使用单引号或者双引号定义一个字符串变量，或者使用三引号定义格式字符串。Dart 的字符串使用 UTF-16 编码</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> s1 = <span class="string">"hello"</span>;</span><br><span class="line"><span class="keyword">var</span> s2 = <span class="string">'world'</span>;</span><br><span class="line"><span class="keyword">var</span> s3 = <span class="string">'hello'</span> + <span class="string">'world'</span>;</span><br><span class="line"><span class="keyword">var</span> s4 = <span class="string">'hello'</span> <span class="string">'world'</span>;</span><br><span class="line"><span class="keyword">var</span> s5 = <span class="string">"""hello </span></span><br><span class="line"><span class="string">              world"""</span>;</span><br></pre></td></tr></tbody></table></figure><p>如果要使用 UTF-32 编码，则要通过 Runes（符号文字），它可以把文字转换成符号表情或者特定文字。</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> clapping = <span class="string">'\u{1f44f}'</span>;</span><br><span class="line"><span class="built_in">print</span>(clapping);</span><br><span class="line"></span><br><span class="line">Runes runes = <span class="keyword">new</span> Runes(<span class="string">'\u{1f44d}'</span>);</span><br><span class="line"><span class="built_in">print</span>(<span class="keyword">new</span> <span class="built_in">String</span>.fromCharCode(runes.first));</span><br></pre></td></tr></tbody></table></figure><p>上面的输出为</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">👏</span><br><span class="line">👍</span><br></pre></td></tr></tbody></table></figure><h3 id="Boolean"><a href="#Boolean" class="headerlink" title="Boolean"></a>Boolean</h3><p>提供 bool 用来声明布尔型变量，默认为 false。</p><h3 id="List-和-Map"><a href="#List-和-Map" class="headerlink" title="List 和 Map"></a>List 和 Map</h3><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">List</span><<span class="built_in">int</span>> l = [<span class="number">1</span>,<span class="number">2</span>,<span class="number">3</span>];</span><br><span class="line">l.forEach((x) => <span class="built_in">print</span>(x)); <span class="comment">//forEach的参数为 Function</span></span><br><span class="line"><span class="keyword">for</span>(<span class="keyword">var</span> x <span class="keyword">in</span> l) { <span class="comment">//使用for-in</span></span><br><span class="line">  <span class="built_in">print</span>(x);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">Map</span><<span class="built_in">int</span>, <span class="built_in">String</span>> map = {<span class="number">0</span>: <span class="string">"a"</span>, <span class="number">1</span>: <span class="string">"b"</span>};</span><br><span class="line">map[<span class="number">0</span>] = <span class="string">"c"</span>;</span><br></pre></td></tr></tbody></table></figure><h2 id="Function"><a href="#Function" class="headerlink" title="Function"></a>Function</h2><h3 id="普通Function"><a href="#普通Function" class="headerlink" title="普通Function"></a>普通Function</h3><p>函数或者方法。和Java 不同，Dart 中方法是有类型的，属于 Function。</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> main() {</span><br><span class="line">  single1(<span class="string">'Tom'</span>, <span class="number">20</span>);</span><br><span class="line">  single2(<span class="string">'Tom'</span>, <span class="number">20</span>);</span><br><span class="line">  single3(<span class="string">'Tom'</span>, <span class="number">20</span>, weight: <span class="number">30</span>); <span class="comment">//由于没有位置约束，必须指定形参名称</span></span><br><span class="line">  single4(<span class="string">'Tom'</span>, <span class="number">20</span>, <span class="number">20</span>, <span class="number">30</span>);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="built_in">bool</span> single1(<span class="built_in">String</span> name, <span class="built_in">int</span> age) {</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//返回类型和参数类型可省略，支持返回表达式</span></span><br><span class="line">single2(name, age) => <span class="keyword">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//可选命名参数，调用时没有顺序要求，同时可选参数可以指定默认值</span></span><br><span class="line"><span class="built_in">bool</span> single3(<span class="built_in">String</span> name, <span class="built_in">int</span> age, {<span class="built_in">int</span> weight = <span class="number">60</span>, <span class="built_in">int</span> height}) => <span class="keyword">true</span>;</span><br><span class="line"></span><br><span class="line"><span class="comment">//可选位置参数，通过位置来确定参数值，要想指定 height 必须先指定 weight</span></span><br><span class="line"><span class="built_in">bool</span> single4(<span class="built_in">String</span> name, <span class="built_in">int</span> age, [<span class="built_in">int</span> weight, <span class="built_in">int</span> height]) => <span class="keyword">true</span>;</span><br></pre></td></tr></tbody></table></figure><p>对于<code>main</code>方法来说，可以定义其为一个有参的方法，同样可以作为入口方法。在 Flutter 项目中的入口方法为：</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> main() => runApp(MyApp());</span><br></pre></td></tr></tbody></table></figure><h3 id="Lambda表达式"><a href="#Lambda表达式" class="headerlink" title="Lambda表达式"></a>Lambda表达式</h3><p>在 Lambda 表达式中，函数可以“没有名字”，同样，也可以像 kotlin 一样，定义一个函数变量</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">var</span> f = (c) {</span><br><span class="line">  <span class="built_in">print</span>(c);</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line">f(<span class="string">'hello'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> list = [<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>];</span><br><span class="line">printElement(x) {<span class="comment">//方法签名写成 void printElement(int x) 更直观</span></span><br><span class="line">  <span class="built_in">print</span>(x);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">list.forEach(printElement);</span><br></pre></td></tr></tbody></table></figure><p><code>forEach</code>的函数定义如下</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">void</span> forEach(<span class="keyword">void</span> f(E element)) {</span><br><span class="line">  <span class="keyword">for</span> (E element <span class="keyword">in</span> <span class="keyword">this</span>) f(element);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p>下面的例子直观展示将函数作为变量传递的思想</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="built_in">Function</span> makeAdder(<span class="built_in">num</span> n) {</span><br><span class="line">  <span class="keyword">return</span> (<span class="built_in">num</span> i) => n + i;</span><br><span class="line">}</span><br><span class="line">  </span><br><span class="line"><span class="comment">//更明晰的写法</span></span><br><span class="line"><span class="built_in">Function</span> makeAdder_(<span class="built_in">num</span> n) {</span><br><span class="line">  <span class="built_in">Function</span> add = (<span class="built_in">num</span> i) {</span><br><span class="line">    <span class="keyword">return</span> i + n;</span><br><span class="line">  };</span><br><span class="line">  <span class="keyword">return</span> add;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">var</span> adder2 = makeAdder(<span class="number">2</span>);</span><br><span class="line"><span class="built_in">print</span>(adder2(<span class="number">3</span>));</span><br></pre></td></tr></tbody></table></figure><p>控制台将输出 5</p><h2 id="运算符"><a href="#运算符" class="headerlink" title="运算符"></a>运算符</h2><h3 id="赋值操作符"><a href="#赋值操作符" class="headerlink" title="赋值操作符"></a>赋值操作符</h3><p>除了<code>=</code>，还有<code>??=</code>，表示如果左边的变量为 null，则将右边的值赋予它，否则左边值不变。</p><h3 id="相等"><a href="#相等" class="headerlink" title="相等"></a>相等</h3><p><code>==</code>将比较两个对象的属性是否相等，判断是否为同一对象使用的是预定义的<code>identical</code>方法</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">external</span> <span class="built_in">bool</span> identical(<span class="built_in">Object</span> a, <span class="built_in">Object</span> b);</span><br></pre></td></tr></tbody></table></figure><h3 id="除法"><a href="#除法" class="headerlink" title="除法"></a>除法</h3><table><thead><tr><th style="text-align:center">操作符</th><th>含义</th></tr></thead><tbody><tr><td style="text-align:center">/</td><td>除，比如 5/2 = 2.5</td></tr><tr><td style="text-align:center">~/</td><td>整除， 5/2 = 2</td></tr></tbody></table><h3 id="类型判断"><a href="#类型判断" class="headerlink" title="类型判断"></a>类型判断</h3><table><thead><tr><th style="text-align:center">操作符</th><th>含义</th></tr></thead><tbody><tr><td style="text-align:center">is</td><td>对象属于指定类型则返回true</td></tr><tr><td style="text-align:center">is!</td><td>对象不属于指定类型返回true</td></tr><tr><td style="text-align:center">as</td><td>类型转换</td></tr></tbody></table><h3 id="条件表达式"><a href="#条件表达式" class="headerlink" title="条件表达式"></a>条件表达式</h3><p>分为两种</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line">condition ? expr1 : expr2<span class="comment">//通用表达式</span></span><br><span class="line">expr1 ?? expr2<span class="comment">//如果 expr1 非空，返回其值，否则返回 expr2</span></span><br></pre></td></tr></tbody></table></figure><h3 id="级联调用与非空调用"><a href="#级联调用与非空调用" class="headerlink" title="级联调用与非空调用"></a>级联调用与非空调用</h3><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//常规写法</span></span><br><span class="line"><span class="keyword">var</span> button = <span class="built_in">querySelector</span>(<span class="string">'#button'</span>);</span><br><span class="line">button.text = <span class="string">'Confirm'</span>;</span><br><span class="line">button.classes.add(<span class="string">'important'</span>);</span><br><span class="line">button.onClick.listen((e) => <span class="built_in">window</span>.alert(<span class="string">'Confirmed!'</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">//使用级联表达式</span></span><br><span class="line"><span class="built_in">querySelector</span>(<span class="string">'#button'</span>) <span class="comment">// Get an object.</span></span><br><span class="line">  ..text = <span class="string">'Confirm'</span>   <span class="comment">// Use its members.</span></span><br><span class="line">  ..classes.add(<span class="string">'important'</span>)</span><br><span class="line">  ..onClick.listen((e) => <span class="built_in">window</span>.alert(<span class="string">'Confirmed!'</span>));</span><br><span class="line"></span><br><span class="line"><span class="comment">//非空调用</span></span><br><span class="line"><span class="built_in">print</span>(button?.text);</span><br></pre></td></tr></tbody></table></figure><h2 id="其他"><a href="#其他" class="headerlink" title="其他"></a>其他</h2><p>Dart 语言在比如 <code>if/else</code>、<code>[do、]while</code>、<code>for</code>、<code>switch/case</code>等语句上跟 Java 类似，不再赘述。</p><p>异常处理的做法如下</p><figure class="highlight dart"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//抛出异常</span></span><br><span class="line"><span class="keyword">throw</span> <span class="string">'x should be less than 10'</span>;</span><br><span class="line"><span class="keyword">throw</span> <span class="keyword">new</span> FormatException(<span class="string">'Expected at least 1 section'</span>);</span><br><span class="line"></span><br><span class="line"><span class="comment">//捕获异常</span></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line">  breedMoreLlamas();</span><br><span class="line">} on OutOfLlamasException {</span><br><span class="line">  buyMoreLlamas();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//...</span></span><br><span class="line">} on Exception <span class="keyword">catch</span> (e) {</span><br><span class="line">  <span class="built_in">print</span>(<span class="string">'Unknown exception: <span class="subst">$e<span class="string">');</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">} catch (e, s) {</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  print('</span></span>Exception details:\n <span class="subst">$e<span class="string">');</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">  print('</span></span>Stack trace:\n <span class="subst">$s<span class="string">');</span></span></span></span><br><span class="line"><span class="string"><span class="subst"><span class="string">}</span></span></span></span><br></pre></td></tr></tbody></table></figure><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;终于开始 Flutter 的具体学习，一切从 Dart 语言开始。&lt;/p&gt;
&lt;figure class=&quot;highlight plain&quot;&gt;&lt;table&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td class=&quot;code&quot;&gt;&lt;pre&gt;&lt;span class=&quot;line&quot;&gt;void main() {&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;    print(&#39;hello world&#39;);&lt;/span&gt;&lt;br&gt;&lt;span class=&quot;line&quot;&gt;}&lt;/span&gt;&lt;br&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;/figure&gt;
    
    </summary>
    
      <category term="Dart" scheme="http://yoursite.com/categories/Dart/"/>
    
    
      <category term="Dart" scheme="http://yoursite.com/tags/Dart/"/>
    
  </entry>
  
  <entry>
    <title>Android Binder 源码解析</title>
    <link href="http://yoursite.com/Android-Binder-%E6%BA%90%E7%A0%81%E8%A7%A3%E6%9E%90/"/>
    <id>http://yoursite.com/Android-Binder-源码解析/</id>
    <published>2019-03-23T04:18:53.000Z</published>
    <updated>2019-04-24T05:31:52.000Z</updated>
    
    <content type="html"><![CDATA[<p>TODO</p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      
      
        &lt;p&gt;TODO&lt;/p&gt;
&lt;script&gt;
        document.querySelectorAll(&#39;.github-emoji&#39;)
          .forEach(el =&gt; {
            if (!el.dataset.src) { return
      
    
    </summary>
    
      <category term="Android" scheme="http://yoursite.com/categories/Android/"/>
    
    
      <category term="Android" scheme="http://yoursite.com/tags/Android/"/>
    
      <category term="Binder" scheme="http://yoursite.com/tags/Binder/"/>
    
  </entry>
  
  <entry>
    <title>Java 线程和线程池详解</title>
    <link href="http://yoursite.com/Java-%E7%BA%BF%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E6%B1%A0%E8%AF%A6%E8%A7%A3/"/>
    <id>http://yoursite.com/Java-线程和线程池详解/</id>
    <published>2019-03-20T09:29:06.000Z</published>
    <updated>2019-06-02T05:02:18.000Z</updated>
    
    <content type="html"><![CDATA[<p>线程在 Java 开发中是一个很重要的概念。 在Java中，“线程”指两件不同的事情：</p><blockquote><p>java.lang.Thread 类的一个实例；</p><p>线程的执行。</p></blockquote><a id="more"></a><h2 id="Java-线程"><a href="#Java-线程" class="headerlink" title="Java 线程"></a>Java 线程</h2><h3 id="线程和进程"><a href="#线程和进程" class="headerlink" title="线程和进程"></a>线程和进程</h3><h4 id="概念"><a href="#概念" class="headerlink" title="概念"></a>概念</h4><p>进程是指一个内存中运行的应用程序，每个进程都有自己独立的一块内存空间，即进程空间（虚空间）。进程不依赖于线程而独立存在，一个进程中可以启动多个线程。系统运行一个程序即是一个进程从创建、运行到消亡的过程。</p><p>线程是指进程中的一个执行流程，一个进程中可以运行多个线程。线程总是属于某个进程，线程没有自己的虚拟地址空间，与进程内的其他线程一起共享分配给该进程的所有资源。，线程是 CPU 执行的基本单位，是花费最小开销的实体。</p><h4 id="区别"><a href="#区别" class="headerlink" title="区别"></a>区别</h4><p>进程有独立的内存空间，进程中的数据存放空间（堆空间和栈空间）是独立的，至少有一个线程。</p><p>线程中堆空间是共享的，栈空间是独立的，线程消耗的资源比进程小的多。</p><blockquote><p>一个进程中的多个线程是并发运行的，从微观角度看存在先后顺序，哪个线程被执行完全取决于 CPU 的调度，程序员无法干涉。这也就造成了多线程的随机性。</p><p>Java 程序的进程里面至少包含两个线程，主线程也就是 main() 方法线程，另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时，实际上都会启动一个 JVM，对应一个进程。</p><p>由于创建一个线程的开销比创建一个进程的开销小的多，在开发多任务运行的时候，通常考虑创建多线程，而不是创建多进程。</p></blockquote><p>实际开发中使用多线程的优势在于</p><ul><li>进程之间不能共享内存，而线程之间可以共享内存。</li><li>系统创建进程需要为该进程重新分配系统资源，创建线程的代价则小的多，因此多任务并发时，多线程效率高。</li><li>Java 语言本身内置多线程功能的支持，而不是单纯作为底层系统的调度方式，从而简化了多线程编程。</li></ul><h3 id="线程的状态"><a href="#线程的状态" class="headerlink" title="线程的状态"></a>线程的状态</h3><p>图一</p><p><img src="/Java-线程和线程池详解/线程状态1.png" alt></p><p>图二</p><p><img src="/Java-线程和线程池详解/线程状态2.png" alt></p><h3 id="分类"><a href="#分类" class="headerlink" title="分类"></a>分类</h3><p>Java 中的线程可以分为用户线程（User Thread）和守护线程（Daemon Thread）。</p><p>只要当前 JVM 实例中存在任何一个非守护线程没有结束，守护线程就全部工作；当最后一个非守护线程结束，即虚拟机中只存在守护线程时，JVM 就会停止运行。Daemon Thread 的作用是为其他线程提供各种服务，最典型的应用就是垃圾收集器。<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Main</span> </span>{</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line">        Thread thread = <span class="keyword">new</span> Thread();</span><br><span class="line">        thread.setDaemon(<span class="keyword">true</span>);</span><br><span class="line">        System.out.println(<span class="string">"is daemon thread? "</span> + thread.isDaemon());</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>输出为<br></p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">is daemon thread? true</span><br></pre></td></tr></tbody></table></figure><p></p><p>使用守护线程要注意的点</p><ul><li><code>thread.setDaemon(true)</code> 必须在 <code>thread.start()</code> 之前设置，否则抛出一个 IllegalThreadStateException 异常。因为不能把正在运行的常规线程设置为守护线程。</li><li>在 Daemon Thread 中产生的新线程也属于 Daemon Thread。 </li><li>不要在 Daemon Thread 中分配读写操作或者计算逻辑任务。 </li></ul><h3 id="Runnable-和-Thread"><a href="#Runnable-和-Thread" class="headerlink" title="Runnable 和 Thread"></a>Runnable 和 Thread</h3><p>Runnable 是一个线程接口，查看其构造<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Runnable</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>其中只定义了一个<code>run</code>方法</p><p>Thread 是实现了 Runnable 接口的类，所有新建 Thread 实例的方法最后都会调用到内部的<code>init</code><br></p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">private void init(ThreadGroup g, Runnable target, String name,</span><br><span class="line">                      long stackSize, AccessControlContext acc,</span><br><span class="line">                      boolean inheritThreadLocals)</span><br></pre></td></tr></tbody></table></figure><p></p><p>观察 Thread 中重写的<code>run</code>方法<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (target != <span class="keyword">null</span>) {</span><br><span class="line">        target.run();</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>其中 target 为 Runnable 对象，即调用 Thread 的<code>run</code>实际上是调用我们传进去的 Runnable 的对应方法</p><p>观察其<code>start</code>方法<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">start</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="comment">//不能重复调用 start 方法</span></span><br><span class="line">    <span class="keyword">if</span> (threadStatus != <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalThreadStateException();</span><br><span class="line">        </span><br><span class="line">    <span class="comment">//将线程加入线程组</span></span><br><span class="line">    group.add(<span class="keyword">this</span>);</span><br><span class="line">    <span class="comment">//线程开始标志</span></span><br><span class="line">    <span class="keyword">boolean</span> started = <span class="keyword">false</span>;</span><br><span class="line">    <span class="keyword">try</span> {</span><br><span class="line">        <span class="comment">//调用native方法开始多线程</span></span><br><span class="line">        start0();</span><br><span class="line">        started = <span class="keyword">true</span>;</span><br><span class="line">    } <span class="keyword">finally</span> {</span><br><span class="line">        <span class="keyword">try</span> {</span><br><span class="line">            <span class="keyword">if</span> (!started) {</span><br><span class="line">                group.threadStartFailed(<span class="keyword">this</span>);</span><br><span class="line">            }</span><br><span class="line">        } <span class="keyword">catch</span> (Throwable ignore) {</span><br><span class="line">            <span class="comment">/* do nothing. If start0 threw a Throwable then</span></span><br><span class="line"><span class="comment">              it will be passed up the call stack */</span></span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>其中<code>start0</code>是 native 方法，在其中新建线程，然后回调 target 的 run 方法。</p><p>也就是说，如果我们直接调用<code>run</code>方法，相当于调用普通方法，不会有多线程效果。只有调用<code>start</code>才会在后台开启另一个线程，等待 CPU 调度。</p><h3 id="多线程-API"><a href="#多线程-API" class="headerlink" title="多线程 API"></a>多线程 API</h3><h4 id="Object"><a href="#Object" class="headerlink" title="Object"></a>Object</h4><p>实际上除了 Thread，Java 的基类 Object 中也定义了一些关于多线程操作的方法</p><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>wait()</td><td>锁对象调用该方法使当前线程进入等待状态，并立刻释放锁对象，直到被其他线程唤醒进入等锁池</td></tr><tr><td>wait(long)</td><td>锁对象调用该方法使当前线程进入等待状态，同时释放锁对象。但是超过等待的时间后线程会自动唤醒，或者被其他线程唤醒，并进入等锁池中。</td></tr><tr><td>wait(long, int)</td><td>和o.wait(long)方法一样，如果int参数大于0则前面的long数字加1000</td></tr><tr><td>notify()</td><td>随机唤醒一个处于等待中的线程（同一个等待阻塞池中）</td></tr><tr><td>notifyAll()</td><td>唤醒所有等待中的线程（同一个等待阻塞池中）</td></tr></tbody></table><p>以上的方法必须写在 synchronized 方法内部或者 synchronized 块内部，因为它们要求当前正在运行<code>object.wait()</code>方法的线程拥有 object 的对象锁，否则抛出异常，测试代码如下<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ThreadTest</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line">        Thread t = Thread.currentThread();</span><br><span class="line">        <span class="keyword">try</span> {</span><br><span class="line">            t.wait(<span class="number">2000</span>);   <span class="comment">//由于没有获得锁，将抛出 IllegalMonitorStateException 异常</span></span><br><span class="line">        } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">        System.out.println(<span class="string">"current Thread info in Main: "</span> + t.toString());</span><br><span class="line">        A a = <span class="keyword">new</span> A();</span><br><span class="line">        a.printThreadInfo();</span><br><span class="line">    }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">A</span> </span>{</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">printThreadInfo</span><span class="params">()</span> </span>{</span><br><span class="line">        Thread t = Thread.currentThread();</span><br><span class="line">        <span class="keyword">try</span> {</span><br><span class="line">            wait(<span class="number">2000</span>);</span><br><span class="line">        } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line">            e.printStackTrace();</span><br><span class="line">        }</span><br><span class="line">        System.out.println(<span class="string">"current Thread info in A: "</span> + t.toString());</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>删除<code>main</code>中 try/catch 语句之后，控制台输出如下<br></p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">current Thread info in Main: Thread[main,5,main]</span><br><span class="line">//等待两秒</span><br><span class="line">current Thread info in A: Thread[main,5,main]</span><br></pre></td></tr></tbody></table></figure><p></p><p><strong>为什么需要在 synchronized 中？</strong></p><blockquote><p>wait和notify用于线程间通信。<br>以生产者消费者模式举例，生产者和消费者通过队列进行通信，对于队列的操作要保证线程安全性</p><p>一般对队列的操作如下:<br>while(queue.size() == MAX_SIZE){ wait() }</p><p>假如不对这段代码加锁，就会出现问题。模拟一个生产者线程t1和一个消费者线程t2</p><ul><li><p>t1判断队列满，需要 wait 阻塞线程。</p></li><li><p>但是就在t1还没有调用 wait 的时候，消费者t2消费了一个产品，导致队列非满。</p></li><li><p>这时候生产者线程t1调用 wait 阻塞，造成的情况就是队列非满，但是生产者线程阻塞了。</p></li><li><p>假如此时消费者不消费了，那么生产者则会一直阻塞下去。</p></li><li><p>所以在调用 wait、notify 以及 notifyAll 等方法时一定要进行同步处理。</p></li></ul></blockquote><p><strong>为什么定义在 Object 中？</strong></p><blockquote><p>Object 中的<code>wait()</code>, <code>notify()</code>等方法，和 synchronized 一样，会对“对象的同步锁”进行操作。</p><p><code>wait()</code>会使“当前线程”等待。进入等待状态时，线程应该释放它锁持有的“同步锁”，否则其它线程获取不到该“同步锁”将无法运行！<br>当线程释放它持有的“同步锁”之后变成等待线程，可以被<code>notify()</code>或<code>notifyAll()</code>唤醒。那么，<code>notify()</code>依据什么唤醒等待线程的？或者说，<code>wait()</code>等待线程和<code>notify()</code>之间通过什么关联起来？答案是：依据“对象的同步锁”。</p><p>负责唤醒等待线程的那个线程(“唤醒线程”)，只有在获取“该对象的同步锁”(这里的同步锁必须和等待线程的同步锁是同一个)，并且调用<code>notify()</code>或<code>notifyAll()</code>方法之后，才能唤醒等待线程。此时因为唤醒线程还持有“该对象的同步锁”，所以必须等到唤醒线程释放了“对象的同步锁”之后，等待线程才能获取到“对象的同步锁”进而继续运行。</p><p>总之，<code>notify()</code>, <code>wait()</code>依赖于“同步锁”，而“同步锁”是对象锁持有，并且每个对象有且仅有一个！</p><p>这就是<code>notify()</code>, <code>wait()</code>等函数定义在 Object 类，而不是 Thread 类中的原因。</p><p>来自 <a href="https://www.cnblogs.com/happy-coder/p/6587092.html" target="_blank" rel="noopener">JAVA 线程状态及转化</a></p></blockquote><h4 id="Thread"><a href="#Thread" class="headerlink" title="Thread"></a>Thread</h4><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>Thread.currentThread()</td><td>返回对当前线程对象的引用</td></tr><tr><td>Thread.interrupted()</td><td>检测当前线程是否已经中断（调用该方法后将该线程的中断标志位设为false，连续两次调用该方法第二次肯定为false）</td></tr><tr><td>Thread.sleep(long millis)</td><td>使当前线程睡眠（不会释放锁对象，可以让其他线程有执行的机会）</td></tr><tr><td>Thread.yield()</td><td>使当前线程放弃cpu的执行权（有可能立刻又被重新选中继续执行，只可能给优先级更高的线程机会）</td></tr><tr><td>t.getId()…</td><td>返回该线程的 id 等等信息</td></tr><tr><td>t.interrupt()</td><td>将该线程中断（实际并不会中断，只是将中断标志设置为true）</td></tr><tr><td>t.isInterrupted()</td><td>检测该线程是否已经中断</td></tr><tr><td>t.join()</td><td>在a线程中调用b.join()，则a线程阻塞，直到b线程执行完</td></tr><tr><td>t.join(long millis)</td><td>同上，不过a线程阻塞的时间根据long的大小有关，如果达到设定的阻塞时间，就算b线程没有执行完，a线程也会被唤醒。</td></tr></tbody></table><hr><p>关于 interrupt，<a href="https://blog.csdn.net/qpc908694753/article/details/61414495" target="_blank" rel="noopener">JAVA interrupt、interrupted和isInterrupted的区别</a></p><blockquote><p>interrupt 方法是用于中断线程的，调用该方法的线程的状态将被置为”中断”状态。</p><p>注意：线程中断仅仅是设置线程的中断状态位，不会停止线程。需要用户自己去监视线程的状态并做处理。支持线程中断的方法（也就是线程中断后会抛出 InterruptedException 的方法，比如这里的sleep，以及Object.wait等方法）就是在监视线程的中断状态，一旦线程的中断状态被置为“中断状态”，就会抛出中断异常。</p></blockquote><h4 id="废弃方法"><a href="#废弃方法" class="headerlink" title="废弃方法"></a>废弃方法</h4><ul><li>stop：会释放该线程所持有的所有锁，但这种释放是不可控制、非预期的。而且一个线程不应该由其他线程来强制中断或停止，而应该自行停止</li><li>suspend：线程在暂停的时候仍然占有该资源，导致需要该资源的线程产生环路等待，从而造成死锁。</li><li>resume：用来回复被挂起的线程，跟 suspend 对应。</li></ul><h2 id="线程池"><a href="#线程池" class="headerlink" title="线程池"></a>线程池</h2><p>Java 中关于线程池的继承关系如下</p><p><img src="/Java-线程和线程池详解/继承图.png" alt></p><p>Executor 是一个顶层接口，其中只声明了一个方法<code>execute(Runnable)</code>，用来执行传进去的任务</p><p>ExecutorService 接口继承了 Executor 接口，并声明了一些方法：<code>submit</code>、<code>invokeAll</code>、<code>invokeAny</code>以及<code>shutDown</code> 等</p><p>抽象类 AbstractExecutorService 实现了 ExecutorService 接口，基本实现了 ExecutorService 中声明的所有方法</p><p>ThreadPoolExecutor 继承了类 AbstractExecutorService，是线程池实现类，构造方法如下<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ThreadPoolExecutor</span><span class="params">(<span class="keyword">int</span> corePoolSize,</span></span></span><br><span class="line"><span class="function"><span class="params">                          <span class="keyword">int</span> maximumPoolSize,</span></span></span><br><span class="line"><span class="function"><span class="params">                          <span class="keyword">long</span> keepAliveTime,</span></span></span><br><span class="line"><span class="function"><span class="params">                          TimeUnit unit,</span></span></span><br><span class="line"><span class="function"><span class="params">                          BlockingQueue<Runnable> workQueue,</span></span></span><br><span class="line"><span class="function"><span class="params">                          ThreadFactory threadFactory,</span></span></span><br><span class="line"><span class="function"><span class="params">                          RejectedExecutionHandler handler)</span> </span>{</span><br><span class="line">    <span class="keyword">if</span> (corePoolSize < <span class="number">0</span> ||</span><br><span class="line">        maximumPoolSize <= <span class="number">0</span> ||</span><br><span class="line">        maximumPoolSize < corePoolSize ||</span><br><span class="line">        keepAliveTime < <span class="number">0</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalArgumentException();</span><br><span class="line">    <span class="keyword">if</span> (workQueue == <span class="keyword">null</span> || threadFactory == <span class="keyword">null</span> || handler == <span class="keyword">null</span>)</span><br><span class="line">        <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">    <span class="keyword">this</span>.corePoolSize = corePoolSize;</span><br><span class="line">    <span class="keyword">this</span>.maximumPoolSize = maximumPoolSize;</span><br><span class="line">    <span class="keyword">this</span>.workQueue = workQueue;</span><br><span class="line">    <span class="keyword">this</span>.keepAliveTime = unit.toNanos(keepAliveTime);</span><br><span class="line">    <span class="keyword">this</span>.threadFactory = threadFactory;</span><br><span class="line">    <span class="keyword">this</span>.handler = handler;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><ul><li>corePoolSize：核心池的大小，创建了线程池后，默认情况下，线程池中并没有任何线程，而是等待有任务到来才创建线程去执行任务，除非调用<code>prestartAllCoreThreads</code>或者<code>prestartCoreThread</code>方法预创建线程，即在没有任务到来之前就创建 corePoolSize 个线程或者一个线程。当线程池中的线程数目达到 corePoolSize 后，到达的任务会被放到缓存队列中</li><li>maximumPoolSize：线程池最大线程数，表示在线程池中最多能创建多少个线程</li><li>keepAliveTime：线程没有任务执行时最多保持多久时间会终止。默认情况下，当线程池中的线程数大于 corePoolSize 时，如果一个线程空闲的时间达到 keepAliveTime，就会终止，直到线程池中的线程数不超过 corePoolSize。但如果调用了<code>allowCoreThreadTimeOut(boolean)</code>方法，即使线程数不大于 corePoolSize，该参数也会起作用，直到线程池中的线程数为0</li><li><p>unit：参数keepAliveTime的时间单位，有7种取值</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">TimeUnit.DAYS;               //天</span><br><span class="line">TimeUnit.HOURS;             //小时</span><br><span class="line">TimeUnit.MINUTES;           //分钟</span><br><span class="line">TimeUnit.SECONDS;           //秒</span><br><span class="line">TimeUnit.MILLISECONDS;      //毫秒</span><br><span class="line">TimeUnit.MICROSECONDS;      //微妙</span><br><span class="line">TimeUnit.NANOSECONDS;       //纳秒</span><br></pre></td></tr></tbody></table></figure></li><li><p>workQueue：阻塞队列，用来存储等待执行的任务，会对线程池的运行过程产生重大影响。一般来说有以下几种选择：</p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">ArrayBlockingQueue;</span><br><span class="line">LinkedBlockingQueue;</span><br><span class="line">SynchronousQueue;</span><br></pre></td></tr></tbody></table></figure></li><li><p>threadFactory：线程工厂，主要用来创建线程</p></li><li>handler：表示当拒绝处理任务时的策略，有以下四种选择：<figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">ThreadPoolExecutor.AbortPolicy：丢弃任务并抛出 RejectedExecutionException 异常。 </span><br><span class="line">ThreadPoolExecutor.DiscardPolicy：也是丢弃任务，但是不抛出异常。 </span><br><span class="line">ThreadPoolExecutor.DiscardOldestPolicy：丢弃队列最前面的任务，然后重新尝试执行任务（重复此过程）</span><br><span class="line">ThreadPoolExecutor.CallerRunsPolicy：由调用线程处理该任务</span><br></pre></td></tr></tbody></table></figure></li></ul><h4 id="为何使用"><a href="#为何使用" class="headerlink" title="为何使用"></a>为何使用</h4><ul><li>降低资源消耗<br><br>可以重复利用已创建的线程降低线程创建和销毁造成的消耗。 </li><li>提高响应速度<br><br>当任务到达时，任务可以不需要等到线程创建就能立即执行。 </li><li>提高线程的可管理性 <br><br>线程是稀缺资源，如果无限制地创建，不仅会消耗系统资源，还会降低系统的稳定性，使用线程池可以进行统一分配、调优和监控</li></ul><h4 id="如何使用"><a href="#如何使用" class="headerlink" title="如何使用"></a>如何使用</h4><p>实际上新建线程池都通过工厂类 Executors 类实现，其中定义一些新建 ThreadPoolExecutor 实例的工厂方法<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="comment">//创建可容纳固定数量线程的线程池，每个线程的存活时间是无限的</span></span><br><span class="line"><span class="comment">//线程池满了就不再添加线程；</span></span><br><span class="line"><span class="comment">//如果池中的所有线程均在繁忙状态，对于新任务会进入阻塞队列中(无界的阻塞队列)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newFixedThreadPool</span><span class="params">(<span class="keyword">int</span> nThreads, ThreadFactory threadFactory)</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> ThreadPoolExecutor(nThreads, nThreads,</span><br><span class="line">                                  <span class="number">0L</span>, TimeUnit.MILLISECONDS,</span><br><span class="line">                                  <span class="keyword">new</span> LinkedBlockingQueue<Runnable>(),</span><br><span class="line">                                  threadFactory);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//有新任务到来，则插入到SynchronousQueue中，由于SynchronousQueue是同步队列</span></span><br><span class="line"><span class="comment">//因此会在池中寻找可用线程来执行，若有可以线程则执行，若没有可用线程则创建一个线程来执行该任务</span></span><br><span class="line"><span class="comment">//若池中线程空闲时间超过指定大小，则该线程会被销毁。</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newCachedThreadPool</span><span class="params">(ThreadFactory threadFactory)</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> ThreadPoolExecutor(<span class="number">0</span>, Integer.MAX_VALUE,</span><br><span class="line">                                  <span class="number">60L</span>, TimeUnit.SECONDS,</span><br><span class="line">                                  <span class="keyword">new</span> SynchronousQueue<Runnable>(),</span><br><span class="line">                                  threadFactory);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//创建只有一个线程的线程池，且线程的存活时间是无限的</span></span><br><span class="line"><span class="comment">//当该线程正繁忙时，对于新任务会进入阻塞队列中(无界的阻塞队列)</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newSingleThreadExecutor</span><span class="params">(ThreadFactory threadFactory)</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> FinalizableDelegatedExecutorService</span><br><span class="line">        (<span class="keyword">new</span> ThreadPoolExecutor(<span class="number">1</span>, <span class="number">1</span>,</span><br><span class="line">                                <span class="number">0L</span>, TimeUnit.MILLISECONDS,</span><br><span class="line">                                <span class="keyword">new</span> LinkedBlockingQueue<Runnable>(),</span><br><span class="line">                                threadFactory));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//创建一个固定大小的线程池，线程池内线程存活时间无限制，线程池可以支持定时及周期性任务执行</span></span><br><span class="line"><span class="comment">//如果所有线程均处于繁忙状态，对于新任务会进入DelayedWorkQueue队列中</span></span><br><span class="line"><span class="comment">//这是一种按照超时时间排序的队列结构</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ScheduledExecutorService <span class="title">newScheduledThreadPool</span><span class="params">(</span></span></span><br><span class="line"><span class="function"><span class="params">        <span class="keyword">int</span> corePoolSize, ThreadFactory threadFactory)</span> </span>{</span><br><span class="line">    <span class="keyword">return</span> <span class="keyword">new</span> ScheduledThreadPoolExecutor(corePoolSize, threadFactory);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>ThreadPoolExecutor 中比较重要的方法</p><table><thead><tr><th>方法</th><th>描述</th></tr></thead><tbody><tr><td>execute()</td><td>向线程池提交一个任务，交由线程池去执行</td></tr><tr><td>submit()</td><td>向线程池提交任务的，能够返回任务执行的结果（利用 Future）</td></tr><tr><td>shutdown()</td><td>关闭线程池</td></tr><tr><td>shutdownNow()</td><td>关闭线程池</td></tr></tbody></table><p>还有其他的方法比如：<code>getQueue()</code>、<code>getPoolSize()</code> 、<code>getActiveCount()</code>、<code>getCompletedTaskCount()</code>等用来获取线程池的相关属性。</p><p>简单使用<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line">    ExecutorService executor = Executors.newSingleThreadExecutor();</span><br><span class="line">    System.out.println(Thread.currentThread());</span><br><span class="line">    executor.execute(()-> {</span><br><span class="line">        System.out.println(Thread.currentThread());</span><br><span class="line">    });</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>控制台输出<br></p><figure class="highlight plain"><table><tbody><tr><td class="code"><pre><span class="line">Thread[main,5,main]</span><br><span class="line">Thread[pool-1-thread-1,5,main]</span><br></pre></td></tr></tbody></table></figure><p></p><h4 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h4><p>观察 ThreadPoolExecutor 中的<code>execute</code>方法<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(Runnable command)</span> </span>{</span><br><span class="line">        <span class="keyword">if</span> (command == <span class="keyword">null</span>)</span><br><span class="line">            <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line">  </span><br><span class="line">        <span class="comment">/**</span></span><br><span class="line"><span class="comment">         * 一、判断当前活跃线程数是否小于 corePoolSize，如果小于，调用 addWorker 创建线程执行任务</span></span><br><span class="line"><span class="comment">         * 二、如果大于 corePoolSize，将任务添加到 workQueue 队列。</span></span><br><span class="line"><span class="comment">         * 三、如果加入 workQueue 失败，则创建线程执行任务，</span></span><br><span class="line"><span class="comment">         *     如果创建线程失败(当前线程数大于maximumPoolSize)，就会调用reject(内部用handler)处理拒绝任务。</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">int</span> c = ctl.get();</span><br><span class="line">        <span class="keyword">if</span> (workerCountOf(c) < corePoolSize) {</span><br><span class="line">            <span class="keyword">if</span> (addWorker(command, <span class="keyword">true</span>))</span><br><span class="line">                <span class="keyword">return</span>;</span><br><span class="line">            c = ctl.get();</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">if</span> (isRunning(c) && workQueue.offer(command)) {</span><br><span class="line">            <span class="keyword">int</span> recheck = ctl.get();</span><br><span class="line">            <span class="keyword">if</span> (! isRunning(recheck) && remove(command))</span><br><span class="line">                reject(command);</span><br><span class="line">            <span class="keyword">else</span> <span class="keyword">if</span> (workerCountOf(recheck) == <span class="number">0</span>)</span><br><span class="line">                addWorker(<span class="keyword">null</span>, <span class="keyword">false</span>);</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">else</span> <span class="keyword">if</span> (!addWorker(command, <span class="keyword">false</span>))</span><br><span class="line">            reject(command);</span><br><span class="line">    }</span><br></pre></td></tr></tbody></table></figure><p></p><p>跟踪<code>addWorker</code>方法<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">addWorker</span><span class="params">(Runnable firstTask, <span class="keyword">boolean</span> core)</span> </span>{</span><br><span class="line">    retry:</span><br><span class="line">    <span class="keyword">for</span> (<span class="keyword">int</span> c = ctl.get();;) {</span><br><span class="line">        <span class="comment">// Check if queue empty only if necessary.</span></span><br><span class="line">        <span class="keyword">if</span> (runStateAtLeast(c, SHUTDOWN)</span><br><span class="line">            && (runStateAtLeast(c, STOP)</span><br><span class="line">                || firstTask != <span class="keyword">null</span></span><br><span class="line">                || workQueue.isEmpty()))</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">        <span class="keyword">for</span> (;;) {</span><br><span class="line">            <span class="comment">/*</span></span><br><span class="line"><span class="comment">             *在创建非核心线程，即core等于false时。判断当前线程数是否大于等于maximumPoolSize，</span></span><br><span class="line"><span class="comment">             *如果大于等于则返回false，即上边说的第三步中创建线程失败的情况</span></span><br><span class="line"><span class="comment">             */</span></span><br><span class="line">            <span class="keyword">if</span> (workerCountOf(c)</span><br><span class="line">                >= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">            <span class="keyword">if</span> (compareAndIncrementWorkerCount(c))</span><br><span class="line">                <span class="keyword">break</span> retry;</span><br><span class="line">            c = ctl.get();  <span class="comment">// Re-read ctl</span></span><br><span class="line">            <span class="keyword">if</span> (runStateAtLeast(c, SHUTDOWN))</span><br><span class="line">                <span class="keyword">continue</span> retry;</span><br><span class="line">            <span class="comment">// else CAS failed due to workerCount change; retry inner loop</span></span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">        </span><br><span class="line">    <span class="keyword">boolean</span> workerStarted = <span class="keyword">false</span>;</span><br><span class="line">    <span class="keyword">boolean</span> workerAdded = <span class="keyword">false</span>;</span><br><span class="line">    Worker w = <span class="keyword">null</span>;</span><br><span class="line">    <span class="keyword">try</span> {</span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">         * 创建Worker时会调用threadFactory来创建一个线程。</span></span><br><span class="line"><span class="comment">         * 上边的第二步中中启动一个线程会触发Worker的run方法被线程调用。</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        w = <span class="keyword">new</span> Worker(firstTask);</span><br><span class="line">        <span class="keyword">final</span> Thread t = w.thread;</span><br><span class="line">        <span class="keyword">if</span> (t != <span class="keyword">null</span>) {</span><br><span class="line">            <span class="keyword">final</span> ReentrantLock mainLock = <span class="keyword">this</span>.mainLock;</span><br><span class="line">            mainLock.lock();</span><br><span class="line">            <span class="keyword">try</span> {</span><br><span class="line">                <span class="keyword">int</span> c = ctl.get();</span><br><span class="line">                </span><br><span class="line">                <span class="keyword">if</span> (isRunning(c) ||</span><br><span class="line">                    (runStateLessThan(c, STOP) && firstTask == <span class="keyword">null</span>)) {</span><br><span class="line">                    <span class="keyword">if</span> (t.isAlive()) <span class="comment">// precheck that t is startable</span></span><br><span class="line">                        <span class="keyword">throw</span> <span class="keyword">new</span> IllegalThreadStateException();</span><br><span class="line">                    workers.add(w);</span><br><span class="line">                    <span class="keyword">int</span> s = workers.size();</span><br><span class="line">                    <span class="keyword">if</span> (s > largestPoolSize)</span><br><span class="line">                        largestPoolSize = s;</span><br><span class="line">                    workerAdded = <span class="keyword">true</span>;</span><br><span class="line">                }</span><br><span class="line">            } <span class="keyword">finally</span> {</span><br><span class="line">                mainLock.unlock();</span><br><span class="line">            }</span><br><span class="line">            <span class="keyword">if</span> (workerAdded) {</span><br><span class="line">                t.start();</span><br><span class="line">                workerStarted = <span class="keyword">true</span>;</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">    } <span class="keyword">finally</span> {</span><br><span class="line">        <span class="keyword">if</span> (! workerStarted)</span><br><span class="line">            addWorkerFailed(w);</span><br><span class="line">    }</span><br><span class="line">    <span class="keyword">return</span> workerStarted;</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>新建 Work ，同时也会利用工厂类实例化一个线程<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line">Worker(Runnable firstTask) {</span><br><span class="line">    setState(-<span class="number">1</span>); <span class="comment">// inhibit interrupts until runWorker</span></span><br><span class="line">    <span class="keyword">this</span>.firstTask = firstTask;</span><br><span class="line">    <span class="keyword">this</span>.thread = getThreadFactory().newThread(<span class="keyword">this</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>如果 workerAdded，调用<code>t.start()</code><br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line">    runWorker(<span class="keyword">this</span>);</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>跟踪<code>runWorker</code><br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">final</span> <span class="keyword">void</span> <span class="title">runWorker</span><span class="params">(Worker w)</span> </span>{</span><br><span class="line">    Thread wt = Thread.currentThread();</span><br><span class="line">    Runnable task = w.firstTask;</span><br><span class="line">    w.firstTask = <span class="keyword">null</span>;</span><br><span class="line">    w.unlock(); <span class="comment">// allow interrupts</span></span><br><span class="line">    <span class="keyword">boolean</span> completedAbruptly = <span class="keyword">true</span>;</span><br><span class="line">    <span class="keyword">try</span> {</span><br><span class="line">        <span class="keyword">while</span> (task != <span class="keyword">null</span> || (task = getTask()) != <span class="keyword">null</span>) {</span><br><span class="line">            <span class="keyword">if</span> ((runStateAtLeast(ctl.get(), STOP) ||</span><br><span class="line">                 (Thread.interrupted() &&</span><br><span class="line">                  runStateAtLeast(ctl.get(), STOP))) &&</span><br><span class="line">                !wt.isInterrupted())</span><br><span class="line">                wt.interrupt();</span><br><span class="line">            <span class="keyword">try</span> {</span><br><span class="line">                beforeExecute(wt, task);</span><br><span class="line">                <span class="keyword">try</span> {</span><br><span class="line">                    task.run();</span><br><span class="line">                    afterExecute(task, <span class="keyword">null</span>);</span><br><span class="line">                } <span class="keyword">catch</span> (Throwable ex) {</span><br><span class="line">                    afterExecute(task, ex);</span><br><span class="line">                    <span class="keyword">throw</span> ex;</span><br><span class="line">                }</span><br><span class="line">            } <span class="keyword">finally</span> {</span><br><span class="line">                task = <span class="keyword">null</span>;</span><br><span class="line">                w.completedTasks++;</span><br><span class="line">                w.unlock();</span><br><span class="line">            }</span><br><span class="line">        }</span><br><span class="line">        completedAbruptly = <span class="keyword">false</span>;</span><br><span class="line">    } <span class="keyword">finally</span> {</span><br><span class="line">        processWorkerExit(w, completedAbruptly);</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>可以看到<code>getTask</code>方法不断从 workerQueue 中读取任务然后执行。只要<code>getTask</code>方法不返回 null，循环就不会退出。<br></p><figure class="highlight java"><table><tbody><tr><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> Runnable <span class="title">getTask</span><span class="params">()</span> </span>{</span><br><span class="line">    <span class="keyword">boolean</span> timedOut = <span class="keyword">false</span>; <span class="comment">// Did the last poll() time out?</span></span><br><span class="line">    <span class="keyword">for</span> (;;) {</span><br><span class="line">        <span class="keyword">int</span> c = ctl.get();</span><br><span class="line">        <span class="keyword">if</span> (runStateAtLeast(c, SHUTDOWN)</span><br><span class="line">            && (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {</span><br><span class="line">            decrementWorkerCount();</span><br><span class="line">            <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">        }</span><br><span class="line">        <span class="keyword">int</span> wc = workerCountOf(c);</span><br><span class="line"></span><br><span class="line">        <span class="comment">//是判断当前线程数是否大于 corePoolSize</span></span><br><span class="line">        <span class="keyword">boolean</span> timed = allowCoreThreadTimeOut || wc > corePoolSize;</span><br><span class="line"></span><br><span class="line">        <span class="keyword">if</span> ((wc > maximumPoolSize || (timed && timedOut))</span><br><span class="line">            && (wc > <span class="number">1</span> || workQueue.isEmpty())) {</span><br><span class="line">            <span class="keyword">if</span> (compareAndDecrementWorkerCount(c))</span><br><span class="line">                <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">            <span class="keyword">continue</span>;</span><br><span class="line">        }</span><br><span class="line">        </span><br><span class="line">        <span class="comment">/*</span></span><br><span class="line"><span class="comment">         * 如果当前线程数大于 corePoolSize，调用 workQueue 的poll方法获取任务</span></span><br><span class="line"><span class="comment">         * 超时时间为 keepAliveTime。如果超时，poll返回了null，上边的while循序就会退出</span></span><br><span class="line"><span class="comment">         * 如果当前线程数小于 corePoolSize，调用 workQueue 的take方法阻塞当前</span></span><br><span class="line"><span class="comment">         */</span></span><br><span class="line">        <span class="keyword">try</span> {</span><br><span class="line">            Runnable r = timed ?</span><br><span class="line">                workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :</span><br><span class="line">                workQueue.take();</span><br><span class="line">            <span class="keyword">if</span> (r != <span class="keyword">null</span>)</span><br><span class="line">                <span class="keyword">return</span> r;</span><br><span class="line">            timedOut = <span class="keyword">true</span>;</span><br><span class="line">        } <span class="keyword">catch</span> (InterruptedException retry) {</span><br><span class="line">            timedOut = <span class="keyword">false</span>;</span><br><span class="line">        }</span><br><span class="line">    }</span><br><span class="line">}</span><br></pre></td></tr></tbody></table></figure><p></p><p>最后用一张图总结上述过程</p><p><img src="/Java-线程和线程池详解/线程池流程.png" alt></p><script>        document.querySelectorAll('.github-emoji')          .forEach(el => {            if (!el.dataset.src) { return; }            const img = document.createElement('img');            img.style = 'display:none !important;';            img.src = el.dataset.src;            img.addEventListener('error', () => {              img.remove();              el.style.color = 'inherit';              el.style.backgroundImage = 'none';              el.style.background = 'none';            });            img.addEventListener('load', () => {              img.remove();            });            document.body.appendChild(img);          });      </script>]]></content>
    
    <summary type="html">
    
      &lt;p&gt;线程在 Java 开发中是一个很重要的概念。 在Java中，“线程”指两件不同的事情：&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;java.lang.Thread 类的一个实例；&lt;/p&gt;
&lt;p&gt;线程的执行。&lt;/p&gt;
&lt;/blockquote&gt;
    
    </summary>
    
      <category term="Java" scheme="http://yoursite.com/categories/Java/"/>
    
    
      <category term="Java" scheme="http://yoursite.com/tags/Java/"/>
    
      <category term="多线程" scheme="http://yoursite.com/tags/%E5%A4%9A%E7%BA%BF%E7%A8%8B/"/>
    
      <category term="线程池" scheme="http://yoursite.com/tags/%E7%BA%BF%E7%A8%8B%E6%B1%A0/"/>
    
  </entry>
  
</feed>
