-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlocal-search.xml
327 lines (157 loc) · 368 KB
/
local-search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title>DataLab</title>
<link href="/2022/11/12/DataLab/"/>
<url>/2022/11/12/DataLab/</url>
<content type="html"><![CDATA[<h2 id="一、实验目的"><a href="#一、实验目的" class="headerlink" title="一、实验目的"></a>一、实验目的</h2><p>本实验目的是加强学生对位级运算的理解及熟练使用的能力。</p><h2 id="二、报告要求"><a href="#二、报告要求" class="headerlink" title="二、报告要求"></a>二、报告要求</h2><p>本报告要求学生把实验中实现的所有函数逐一进行分析说明,写出实现的依据,也就是推理过程,可以是一个简单的数学证明,也可以是代码分析,根据实现中你的想法不同而异。</p><h2 id="三、函数分析"><a href="#三、函数分析" class="headerlink" title="三、函数分析"></a>三、函数分析</h2><ol><li>bitXor函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>bitNor</th></tr></thead><tbody><tr><td>参数</td><td>int , int</td></tr><tr><td>功能实现</td><td>~(x|y)</td></tr><tr><td>要求</td><td>只能使用 ~ 和&,最大8个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>直接德摩根律:</p><p><code>~(x|y)=~x&~y</code></p><p>非常简单。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">bitNor</span><span class="hljs-params">(<span class="hljs-type">int</span> x, <span class="hljs-type">int</span> y)</span> {<br> <span class="hljs-keyword">return</span> ~x & ~y;<br>}<br></code></pre></td></tr></table></figure><ol start="2"><li>copyLSB函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>copyLSB</th></tr></thead><tbody><tr><td>参数</td><td>int</td></tr><tr><td>功能实现</td><td>将x的所有位都设置为它的最低位的值</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >>,最大5个操作数</td></tr></tbody></table><p><strong>分析:</strong></p><p>利用有符号数的右移拓展符号位,左移补0的性质,先将最后一位移到符号位,然后再移到最低位。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">copyLSB</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span> {<br><br> <span class="hljs-keyword">return</span> ((x << <span class="hljs-number">31</span>)>><span class="hljs-number">31</span>);<br>}<br></code></pre></td></tr></table></figure><ol start="3"><li>isEqual函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>isEqual(x, y)</th></tr></thead><tbody><tr><td>参数</td><td>int , int</td></tr><tr><td>功能实现</td><td>x==y返回1,否则返回0</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >>,最大5个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>直接使用异或逻辑即可。相同为0,相异为1.</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">isEqual</span><span class="hljs-params">(<span class="hljs-type">int</span> x, <span class="hljs-type">int</span> y)</span> {<br> <span class="hljs-keyword">return</span> !(x ^ y);<br>}<br></code></pre></td></tr></table></figure><ol start="4"><li>bitMask函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>bitMask(highbit, lowbit)</th></tr></thead><tbody><tr><td>参数</td><td>int , int</td></tr><tr><td>功能实现</td><td>生成一个掩码,lowbit位至highbit位均为1</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >>,最大16个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>用移位操作,先将最高位到lowbit位全都置为1,其他位为0,然后将highbit位到0最低为全都置为1,其他位置0。两者与运算,正好留下中间的lowbit位到highbit位全为1。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">bitMask</span><span class="hljs-params">(<span class="hljs-type">int</span> highbit, <span class="hljs-type">int</span> lowbit)</span> {<br> <span class="hljs-keyword">return</span> (~<span class="hljs-number">0</span><<lowbit) & ((~<span class="hljs-number">0</span>)+(<span class="hljs-number">1</span><<highbit<<<span class="hljs-number">1</span>)) ;<br>}<br></code></pre></td></tr></table></figure><ol start="5"><li>bitCount函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>bitCount(int x)</th></tr></thead><tbody><tr><td>参数</td><td>int</td></tr><tr><td>功能实现</td><td>计算x中1的数目</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >>,最大40个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>一开始的想法是将x右移,统计最低位是1的次数,然后将结果加起来,但是很明显超过了最大40个操作数,而且代码很长。事实上不需要一次一次的加,可以将每2位作为一组,统计2位中1的个数,例如1101,前两位有2个1,后两位有1个1,最后加起来即可。所以可以这样分组<code>[11][01]</code>,为了让<code>[11]</code>中的两个1相加,可以将<code>[11]</code>和01相与,得到第0位的1,然后将<code>[11]</code>右移一位,得到第1位的1,相加,就得到2。所以1101第一次统计得到的数是这样的:<code>[10][01]</code>代表高两位有2个1,低两位有1个1。然后再把这两组数相加,就能得到3。怎么加,还是一样的,不同的是这次要把<code>[10]</code>与<code>[01]</code>相加,而不是<code>[]</code>的内部相加,也就是现在应该2个数当作一个单位。为了相加,先取低两位,即把1001和0011相与,得到<code>[0001]</code>然后将1001右移两位,同样与0011相与,得到高两位<code>[0010]</code>,最后这两个数对齐相加即可,得到<code>[0011]</code>就是最终结果了。</p><p>按照这个原理就可以实现了。依次是1位一组相加,2位一组相加,4位一组相加,8位一组相加,16位一组相加。最后得到结果。所以依次要和0x55555555,0x33333333,0x0f0f0f0f0f0f0f0f,0x0000ffff,0xffffffff。</p><p>但是不能直接使用32位表示,需要利用移位和或运算先构造出上面几个32位数,然后按照</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">bitCount</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span> {<br> <span class="hljs-type">int</span> mask1 = <span class="hljs-number">0x55</span> << <span class="hljs-number">8</span> | <span class="hljs-number">0x55</span>;<br> <span class="hljs-type">int</span> mask2 = <span class="hljs-number">0x33</span> << <span class="hljs-number">8</span> | <span class="hljs-number">0x33</span>;<br> <span class="hljs-type">int</span> mask4 = <span class="hljs-number">0x0f</span> <<<span class="hljs-number">8</span> |<span class="hljs-number">0x0f</span>;<br> <span class="hljs-type">int</span> mask8 = <span class="hljs-number">0xff</span> << <span class="hljs-number">16</span> | <span class="hljs-number">0xff</span>;<br> <span class="hljs-type">int</span> mask16 = ~<span class="hljs-number">0</span> + (<span class="hljs-number">1</span> << <span class="hljs-number">16</span>);<br> mask1 |= mask1 << <span class="hljs-number">16</span>;<br> mask2 |= mask2 | mask2 <<<span class="hljs-number">16</span>;<br> mask4 |= mask4 | mask4 << <span class="hljs-number">16</span>;<br><br> x = (x&mask1) + ((x>><span class="hljs-number">1</span>)&mask1);<br> x = (x&mask2) + ((x>><span class="hljs-number">2</span>)&mask2);<br> x = (x&mask4) + ((x>><span class="hljs-number">4</span>)&mask4);<br> x = (x&mask8) + ((x>><span class="hljs-number">8</span>)&mask8);<br> x = (x&mask16) + ((x>><span class="hljs-number">16</span>)&mask16);<br><br> <span class="hljs-keyword">return</span> x;<br>}<br></code></pre></td></tr></table></figure><ol start="6"><li>tMax函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>tmax(void)</th></tr></thead><tbody><tr><td>参数</td><td>void</td></tr><tr><td>功能实现</td><td>返回最大的补码</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >> ,最大4个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>最大的补码显示是0x7fffffff,只需要构造出来符号位是0的,其他全1的就可以了。还是利用移位的特性。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">tmax</span><span class="hljs-params">(<span class="hljs-type">void</span>)</span> {<br> <span class="hljs-type">int</span> x = <span class="hljs-number">1</span>;<br> x = x<<<span class="hljs-number">31</span>; <br> <span class="hljs-keyword">return</span> ~x;<br>}<br></code></pre></td></tr></table></figure><ol start="7"><li>isNonNegative函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>isNonNegative(x)</th></tr></thead><tbody><tr><td>参数</td><td>int</td></tr><tr><td>功能实现</td><td>如果x>=0,返回1,否则返回0</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >> ,最大6个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>很简单,只需要判断符号位就可以了,右移31位,在和0x01相与,如果是0,则>=0,否则<0</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-keyword">return</span> !(x>><span class="hljs-number">31</span>&<span class="hljs-number">0x01</span>);<br></code></pre></td></tr></table></figure><ol start="8"><li>addOK(x, y)函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>addOK(x,y)</th></tr></thead><tbody><tr><td>参数</td><td>int,int</td></tr><tr><td>功能实现</td><td>确定x+y是否不溢出</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >> ,最大20个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>x+y只有在x和y同号时会溢出,溢出的结果是正正得负,负负得正,所以先把x+y得值求出来,设为z。溢出得条件是,x与y同号,x与z不同号。所以(x^y)为0,同时~(x^z)为0,同时成立,最后与0x01相与得到末位即可。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> z = x + y;<br><span class="hljs-keyword">return</span> (((x^y)|(~(x^z)))>><span class="hljs-number">31</span>)&<span class="hljs-number">0x01</span>;<br></code></pre></td></tr></table></figure><ol start="9"><li>rempwr2函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>rempwr2(x, n)</th></tr></thead><tbody><tr><td>参数</td><td>int,int</td></tr><tr><td>功能实现</td><td>计算x%(2^n)</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >> ,最大20个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>对于正数来说,计算x%(2^n)很简单,直接取x的后n位就可以了,但是对于负数来说,就不能直接取后n位。所以可以先把负数转成正数,最后加个负号。例如<code>-3%2</code>可以先用<code>3%2</code>,得到1,最后加上负号变成-1。负数转成正数可以利用<code>x = (~s&x) | (s&(~x+1))</code>来转换。s表示将符号位拓展成32位。当s为全0,式子得到x=x,当s为全1,得到x=~x+1,即-x。最后加上负号的操作在利用上面的式子就可以了。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">rempwr2</span><span class="hljs-params">(<span class="hljs-type">int</span> x, <span class="hljs-type">int</span> n)</span> {<br> <span class="hljs-type">int</span> s = x>><span class="hljs-number">31</span>;<br> x = (~s&x) | (s&(~x+<span class="hljs-number">1</span>));<br> x &= (~<span class="hljs-number">0</span>+(<span class="hljs-number">1</span><<n));<br> <span class="hljs-keyword">return</span> ((~s)&x) | (s&(~x+<span class="hljs-number">1</span>));<br>}<br></code></pre></td></tr></table></figure><ol start="10"><li>isLess函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>isLess(x, y)</th></tr></thead><tbody><tr><td>参数</td><td>int,int</td></tr><tr><td>功能实现</td><td>如果x<y,返回1,否则返回0</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >> ,最大24个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>比较x与y,可以用<code>x-y</code>的符号来比较。但是必须注意的一点是符号相异的两个数做减法,可能会溢出。所以要分情况讨论,x与y同号,利用<code>x-y</code>判断;x与异号,直接判断x是不是负数。最后两种情况有1个成立即可,利用或运算。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">isLess</span><span class="hljs-params">(<span class="hljs-type">int</span> x, <span class="hljs-type">int</span> y)</span> {<br> <span class="hljs-type">int</span> c1 = (~(x ^ y)>><span class="hljs-number">31</span> & (x + ~y + <span class="hljs-number">1</span>)>><span class="hljs-number">31</span>)&<span class="hljs-number">0x01</span>; <br> <span class="hljs-type">int</span> c2 = ((x ^ y)>><span class="hljs-number">31</span> & x>><span class="hljs-number">31</span>)&<span class="hljs-number">0x01</span>;<br> <span class="hljs-keyword">return</span> c1 | c2;<br>}<br></code></pre></td></tr></table></figure><ol start="11"><li>absVal函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>absVal(x)</th></tr></thead><tbody><tr><td>参数</td><td>int</td></tr><tr><td>功能实现</td><td>x的绝对值</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >> ,最大20个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>太简单了,判断x的符号位,正数和0不用处理,负数取反,利用<code>~x+1</code>,最后取或就可以了。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">absVal</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span> {<br> <span class="hljs-type">int</span> s = x>><span class="hljs-number">31</span>;<br> <span class="hljs-keyword">return</span> (~s&x) | (s&(~x+<span class="hljs-number">1</span>));<br>}<br></code></pre></td></tr></table></figure><ol start="12"><li>isPower2函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>isPower2(x)</th></tr></thead><tbody><tr><td>参数</td><td>int</td></tr><tr><td>功能实现</td><td>如果x是2的幂,返回1,否则返回0</td></tr><tr><td>要求</td><td>只能使用! ~ & ^ | + << >> ,最大20个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>负数和0显然不是2的任何次幂,正数中,32位只有一位1的才是2的幂。如果x只有1位是1,x加上全1,x从1开始到最高位,全都变为0,最低位到原来的1所在位,全都变为1。再与原来的x想与,一定全为0。如果x不只一位为1,最高位到第一个1之间,一定会在原来的位置有一个1,最后想与,一定不为0。</p><p>如果x是负数或者0,要返回0,判断符号位和<code>!!x</code>的值就行了。最后结果相与。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">int</span> <span class="hljs-title function_">isPower2</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span> {<br> <span class="hljs-type">int</span> r = (!(x&(x+~<span class="hljs-number">0</span>)) ) & ((~(x>><span class="hljs-number">31</span>)&<span class="hljs-number">0x01</span>)&(!!x));<br> <span class="hljs-keyword">return</span> r;<br>}<br></code></pre></td></tr></table></figure><ol start="13"><li>float_neg函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>float_neg(uf)</th></tr></thead><tbody><tr><td>参数</td><td>unsigned int</td></tr><tr><td>功能实现</td><td>计算-f</td></tr><tr><td>要求</td><td>逻辑与或非,位操作,if,while都可以用,最多10个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>浮点数的第一位就是符号位,符号位反过来就能得到<code>-f</code>,当输入Not a Number,阶码全1,尾数非全0。将uf和0x7fffffff相与,小数位至少有一位不是0,就是NaN,此时直接返回。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">unsigned</span> <span class="hljs-title function_">float_neg</span><span class="hljs-params">(<span class="hljs-type">unsigned</span> uf)</span> {<br> <span class="hljs-keyword">if</span>((uf&<span class="hljs-number">0x7fffffff</span>) > <span class="hljs-number">0x7f800000</span>) <span class="hljs-keyword">return</span> uf;<br> <span class="hljs-keyword">return</span> uf ^ <span class="hljs-number">0x80000000</span>;<br>}<br></code></pre></td></tr></table></figure><ol start="14"><li>float_half函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>float_half(uf)</th></tr></thead><tbody><tr><td>参数</td><td>unsigned int</td></tr><tr><td>功能实现</td><td>返回0.5*f的位级表示</td></tr><tr><td>要求</td><td>逻辑与或非,位操作,if,while都可以用,最多30个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>考虑NaN和INF的特殊情况,即指数<code>exp==255</code>,<code>if((uf&0x7fffffff) >= 0x7f800000) </code>,返回参数。考虑为+0/-0的情况,<code>if(!(uf&0x7fffffff))</code>,返回0。</p><p>规格化小数0.5若结果仍为规格化小数,这时候只需要指数-1,其他部分不变即可。<code>(uf&0x807fffff)|(--exp_)<<23</code>。规格化小数乘以0.5,可能结果变为非规格化。即<code>exp==0</code>或<code>exp==1</code>时的情况,这时候只处理小数部分,因为非规格化小数其指数部分为0,右移一位表示乘以0.5,考虑小数舍入,此时要考虑最低两位数的想偶数进位,舍入判断<code>if((uf&0x3) == 0x3) uf = uf + 0x2;</code>。只有当最低两位数为11时才需要向偶数进位舍入。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">unsigned</span> <span class="hljs-title function_">float_half</span><span class="hljs-params">(<span class="hljs-type">unsigned</span> uf)</span> {<br> <span class="hljs-type">int</span> <span class="hljs-built_in">exp</span> = (uf&<span class="hljs-number">0x7f800000</span>) >> <span class="hljs-number">23</span>;<br> <span class="hljs-type">int</span> s = uf&<span class="hljs-number">0x80000000</span>; <br> <span class="hljs-keyword">if</span>((uf&<span class="hljs-number">0x7fffffff</span>) >= <span class="hljs-number">0x7f800000</span>) <span class="hljs-keyword">return</span> uf;<br> <span class="hljs-keyword">if</span>(<span class="hljs-built_in">exp</span> > <span class="hljs-number">1</span>) <span class="hljs-keyword">return</span>(uf&<span class="hljs-number">0x807fffff</span>) |(--<span class="hljs-built_in">exp</span>)<<<span class="hljs-number">23</span>;<br> <span class="hljs-keyword">if</span>((uf&<span class="hljs-number">0x3</span>) == <span class="hljs-number">0x3</span>)uf = uf + <span class="hljs-number">0x2</span>;<br> <span class="hljs-keyword">return</span> ((uf>><span class="hljs-number">1</span>)&<span class="hljs-number">0x007fffff</span>) | s;<br>}<br></code></pre></td></tr></table></figure><ol start="14"><li>float_i2f函数</li></ol><p><strong>函数要求:</strong></p><table><thead><tr><th>函数名</th><th>float_i2f(x)</th></tr></thead><tbody><tr><td>参数</td><td>int</td></tr><tr><td>功能实现</td><td>返回(float)x的位级表示</td></tr><tr><td>要求</td><td>逻辑与或非,位操作,if,while都可以用,最多30个操作数</td></tr></tbody></table><p><strong>实现分析:</strong></p><p>分三部分处理,获取符号位<code>s_ = x&0x80000000</code>,若为负数<code>-x</code>,变为正数,则<code>0x80000000</code>为特殊情况分开处理,考虑特殊情况,<code>0x0</code>和<code>0x80000000</code>,这两种情况直接返回<code>0</code>和<code>0xcf000000</code>。</p><p>获取最高位的<code>1</code>所在位置,<code>while(!(x&(1<<n_))) n_--;</code>。</p><p>若<code>n_ <= 23</code>这个数需要向左移动到小数部分起始位置(将<code>1</code>放在第<code>23</code>位上),<code>if(n_<=23) x<<=(23-n_);</code>。</p><p>若<code>n_ > 23</code>这个数需要向右移动到小数部分起始位置(将<code>1</code>放在第<code>23</code>位上),这时候需要考虑移出部分的舍入问题,若移出部分大于<code>0.5</code>则向上舍入,若小于<code>0.5</code>则向下舍去,若等于<code>0.5</code>则向偶数舍入。</p><p>先将<code>>=0.5</code>情况等同考虑,向上舍入<code>x+=(1<<(n_-24))</code>。若<code>==0.5</code>时,舍入情况若为奇数,我们需要<code>-1</code>操作变为偶数,即将最低位的<code>1</code>变为<code>0</code>,<code>x&=(0xffffffff<<(n_-22))</code>,若向上舍入时最高位产生了进位,还需要加上进位<code>if(x&(1<<n_)) ;else n_++;</code>。之后拼接浮点数就可以了。</p><p><strong>函数实现:</strong></p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs C"><span class="hljs-type">unsigned</span> <span class="hljs-title function_">floatInt2Float</span><span class="hljs-params">(<span class="hljs-type">int</span> x)</span> {<br> <span class="hljs-type">int</span> s_ = x&<span class="hljs-number">0x80000000</span>;<br> <span class="hljs-type">int</span> n_ = <span class="hljs-number">30</span>;<br> <span class="hljs-keyword">if</span>(!x) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">if</span>(x==<span class="hljs-number">0x80000000</span>) <span class="hljs-keyword">return</span> <span class="hljs-number">0xcf000000</span>;<br> <span class="hljs-keyword">if</span>(s_) x = ~x+<span class="hljs-number">1</span>;<br> <span class="hljs-keyword">while</span>(!(x&(<span class="hljs-number">1</span><<n_))) n_--;<br> <span class="hljs-keyword">if</span>(n_<=<span class="hljs-number">23</span>) x<<=(<span class="hljs-number">23</span>-n_);<br> <span class="hljs-keyword">else</span>{<br> x+=(<span class="hljs-number">1</span><<(n_<span class="hljs-number">-24</span>));<br> <span class="hljs-keyword">if</span>(x<<(<span class="hljs-number">55</span>-n_)) ;<span class="hljs-keyword">else</span> x&=(<span class="hljs-number">0xffffffff</span><<(n_<span class="hljs-number">-22</span>));<br> <span class="hljs-keyword">if</span>(x&(<span class="hljs-number">1</span><<n_)) ;<span class="hljs-keyword">else</span> n_++;<br> x >>= (n_<span class="hljs-number">-23</span>);<br> }<br> x=x&<span class="hljs-number">0x007fffff</span>;<br> n_=(n_+<span class="hljs-number">127</span>)<<<span class="hljs-number">23</span>;<br> <span class="hljs-keyword">return</span> x|n_|s_;<br>}<br></code></pre></td></tr></table></figure><h2 id="四、实验总结"><a href="#四、实验总结" class="headerlink" title="四、实验总结"></a>四、实验总结</h2><p>本次实验强化了我对异或逻辑的理解,比如<code>isEqual</code>这个题,我一开始的想法是利用<code>x-y</code>与0来判断,因为<code>-y=~y+1</code>,用的操作数太多了,与同学交流后,发现他直接用的异或,非常合适。生成掩码(<code>bitMask</code>)加强了我对有符号数移位操作的理解,为后面频繁的移位操作做了个铺垫。第一个难题是<code>bitCount</code>,这道题我一开始想利用移位操作,每移一位加一次,但是这样一来操作数大大的超出了40个。经过网上查找博客,发现可以利用移位操作将2位一组相加,然后4位一组相加,不需要一个一个的加,非常的巧妙。rempwr2这道题我的想法是对的,负数先当正数处理,最后加上负号,但是解法不够优雅,通过查找相关文章,发现了负数转成正数可以利用<code>x = (~s&x) | (s&(~x+1))</code>来转换这个小技巧,大大减少操作数。</p><p><code>addOK</code>和<code>isLess</code>两个题差不多,主要提醒我要注意有符号数的符号位情况,溢出时负负得正,正正得负,很简单。浮点数的题,着重在于NaN和INF单独处理,位操作的细节很多,加强了我对浮点的位表示的理解。另外有偶数进位也要特别注意,有”四舍六入五成双”的口诀。</p><p>实验趣味性很强,对软件工程专业来说,这么精细的位操作很少涉及,很有新鲜感,实验的难度设置也很合理,全都搞懂这些题,对机器的位表示的理解真的会提高不少,比在纸上做题要有意思的多,印象也更加深刻。不得不感叹CMU的计算机教育确实非常好。</p>]]></content>
<categories>
<category>CSAPP</category>
</categories>
<tags>
<tag>X86</tag>
<tag>汇编语言</tag>
<tag>C语言</tag>
<tag>CSAPP</tag>
</tags>
</entry>
<entry>
<title>BombLab</title>
<link href="/2022/11/12/BombLab/"/>
<url>/2022/11/12/BombLab/</url>
<content type="html"><![CDATA[<h2 id="Bomb-Lab"><a href="#Bomb-Lab" class="headerlink" title="Bomb Lab"></a>Bomb Lab</h2><p>本学期的系统及程序设计课程有CSAPP配套的实验任务,当然老师的要求是结合网上的资源,能弄懂实验内容即可,Bomb Lab实验是第二个实验任务,我经过一天左右完成了此实验的7个phase,实践时看了大量的网上的解析,因为我们的实验内容在网上的基础上有所修改,因此并没有一个完整的,易懂的博客来解释实验内容,正好老师要求写实验报告,索性我就尝试来写一个完整的Bomb Lab解析,不当之处还请大家指正。</p><h3 id="实验简介"><a href="#实验简介" class="headerlink" title="实验简介"></a>实验简介</h3><p>本实验要求你使用课程所学知识拆除“binary bombs”,增强对程序的机器级表示、汇编语言、调试器和逆向工程等方面原理与技能的掌握。 一个“binary bombs”(二进制炸弹,下文将简称为炸弹)是一个Linux可执行程序,包含了6个阶段(或层次、关卡)。炸弹运行的每个阶段要求你输入一个特定字符串,你的输入符合程序预期的输入,该阶段的炸弹就被拆除引信即解除了,否则炸弹“爆炸”打印输出 “<strong>BOOM!!!</strong>“。实验的目标是拆除尽可能多的炸弹层次。 每个炸弹阶段考察了机器级程序语言的一个不同方面,难度逐级递增:</p><ul><li>阶段1:字符串比较</li><li>阶段2:循环</li><li>阶段3:条件/分支</li><li>阶段4:递归调用和栈</li><li>阶段5:指针</li><li>阶段6:链表/指针/结构</li></ul><p>另外还有一个隐藏阶段,只有当你在第4阶段的解后附加一特定字符串后才会出现。</p><p>为完成二进制炸弹拆除任务,你需要使用<code>gdb</code>调试器和<code>objdump</code>来反汇编炸弹的可执行文件并跟踪调试每一阶段的机器代码,从中理解每一汇编语言代码的行为或作用,进而设法推断拆除炸弹所需的目标字符串。比如在每一阶段的开始代码前和引爆炸弹的函数前设置断点。</p><p>实验语言:C;实验环境:Linux</p><h3 id="准备阶段"><a href="#准备阶段" class="headerlink" title="准备阶段"></a>准备阶段</h3><p>下载好bomb.tar压缩包,解压后有以下三个文件::</p><ul><li>README:标识该bomb和所有者。</li><li>bomb:bomb的可执行程序。</li><li>bomb.c:bomb程序的main函数。</li></ul><h4 id="README文件"><a href="#README文件" class="headerlink" title="README文件"></a>README文件</h4><p>这个文件就一句话,这个炸弹要炸的是你。</p><h4 id="bomb-c文件"><a href="#bomb-c文件" class="headerlink" title="bomb.c文件"></a>bomb.c文件</h4><p>bomb.c文件先介绍了Bomb的版权信息,贴一下机翻,Dr. Evil的美式幽默。</p><figure class="highlight gcode"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs gcode">邪恶博士公司<span class="hljs-comment">(犯罪者)</span>特此授予您<span class="hljs-comment">(受害者)</span>使用此炸弹<span class="hljs-comment">(炸弹)</span>的明确许可。这是一个有时间限制的许可证,在受害者死亡时到期。犯罪者对受害人的伤害、沮丧、精神错乱、眼虫病、腕管综合症、失眠或其他伤害概不负责。除非行凶者想把功劳占为己有。受害者不得将此炸弹源代码分发给行凶者的任何敌人。任何受害者都不能调试、反向工程、运行“字符串”、反编译、解密或使用任何其他技术来获取有关BOMB的信息和拆除炸弹。操作此程序时,请勿穿防炸弹服装。作恶者不会为自己糟糕的幽默感道歉。在法律禁止使用BOMB的地方,本许可证是无效的。<br></code></pre></td></tr></table></figure><p>然后就是bomb可执行程序的main函数了,结构很简单。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdlib.h></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"support.h"</span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string">"phases.h"</span></span><br><br>FILE *infile;<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span> *argv[])</span><br>{<br> <span class="hljs-type">char</span> *input;<br> <span class="hljs-comment">//不带参数运行bomb 程序读取标准输入中</span><br> <span class="hljs-keyword">if</span> (argc == <span class="hljs-number">1</span>) { <br>infile = <span class="hljs-built_in">stdin</span>;<br> } <br> <span class="hljs-comment">//使用一个参数运行bomb 程序读文件直到EOF结束符 所以完成了的phase的答案可以写到文件中 下次就不需要重复输入了</span><br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (argc == <span class="hljs-number">2</span>) {<br><span class="hljs-keyword">if</span> (!(infile = fopen(argv[<span class="hljs-number">1</span>], <span class="hljs-string">"r"</span>))) {<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"%s: Error: Couldn't open %s\n"</span>, argv[<span class="hljs-number">0</span>], argv[<span class="hljs-number">1</span>]);<br> <span class="hljs-built_in">exit</span>(<span class="hljs-number">8</span>);<br>}<br> }<br> <span class="hljs-comment">//你的参数不能多于1个</span><br> <span class="hljs-keyword">else</span> {<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"Usage: %s [<input_file>]\n"</span>, argv[<span class="hljs-number">0</span>]);<br><span class="hljs-built_in">exit</span>(<span class="hljs-number">8</span>);<br> }<br> <br> <span class="hljs-comment">//做了一些秘密事情 让你更难拆除炸弹</span><br> initialize_bomb();<br> <br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Welcome to my fiendish little bomb. You have 6 phases with\n"</span>);<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"which to blow yourself up. Have a nice day!\n"</span>);<br><br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> *阶段1:字符串比较</span><br><span class="hljs-comment"> */</span><br> input = read_line(); <span class="hljs-comment">//获得输入 </span><br> phase_1(input); <span class="hljs-comment">//根据输入调phase_1函数 输入不正确就爆炸了 </span><br> phase_defused(); <span class="hljs-comment">//输入正确调用炸弹被解决函数 </span><br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Phase 1 defused. How about the next one?\n"</span>);<br><span class="hljs-comment">/*</span><br><span class="hljs-comment"> *阶段2:循环</span><br><span class="hljs-comment"> */</span><br> input = read_line();<br> phase_2(input);<br> phase_defused();<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"That's number 2. Keep going!\n"</span>);<br><span class="hljs-comment">/*</span><br><span class="hljs-comment"> *阶段3:条件/分支</span><br><span class="hljs-comment"> */</span><br> input = read_line();<br> phase_3(input);<br> phase_defused();<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Halfway there!\n"</span>);<br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> *阶段4:递归调用和栈</span><br><span class="hljs-comment"> */</span><br> input = read_line();<br> phase_4(input);<br> phase_defused();<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"So you got that one. Try this one.\n"</span>);<br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> *阶段5:指针</span><br><span class="hljs-comment"> */</span> <br> input = read_line();<br> phase_5(input);<br> phase_defused();<br> <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Good work! On to the next...\n"</span>);<br> <span class="hljs-comment">/*</span><br><span class="hljs-comment"> *阶段6:链表/指针/结构</span><br><span class="hljs-comment"> */</span><br> input = read_line();<br> phase_6(input);<br> phase_defused();<br><br> <span class="hljs-comment">/* Wow, they got it! But isn't something... missing? Perhaps</span><br><span class="hljs-comment"> * something they overlooked? Mua ha ha ha ha! */</span><br> <br> <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>问题的关键很清晰了,我们要让main函数顺利执行结束,所以每个<code>phase</code>函数都要正常返回,那么phase函数在哪呢,别慌,我们没有C代码,但是有可执行程序bomb,将bomb反汇编,就可以拿到汇编代码了,所以我们得能看懂汇编代码,遇到不熟悉的指令也不慌,翻一翻书网上查一查就能有结果了。</p><p>当然如果我们会<code>gdb</code>和<code>objdump</code>的常用命令,看汇编代码就会变得更简单。</p><p>objdump命令:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs shell">-d, --disassemble<br>反汇编目标文件,将机器指令反汇编成汇编代码<br>-D, --disassemble-all<br>与 -d 类似,但反汇编所有段(section)<br></code></pre></td></tr></table></figure><p>gdb命令:</p><p><a href="https://www.cnblogs.com/tangtangde12580/p/8045980.html">https://www.cnblogs.com/tangtangde12580/p/8045980.html</a></p><p>使用objdump反汇编bomb文件,重定向到bomb.s文件当中,得到机器指令的汇编代码:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs shell">objdump -d bomb > bomb.s<br></code></pre></td></tr></table></figure><p>使用notepad++或者vscode打开,可以找到6个phase函数的汇编代码(加上secret_phase七个)。接下来就可以正式开始了。</p><p>需要特别注意的是,objdump默认反汇编出的代码指令格式不是<strong>Intel</strong>格式,是<strong>AT&T</strong>格式,也可以在反汇编时使用<code>-M</code>参数指定指令格式。</p><p><strong>x86架构汇编指令一般有两种格式</strong>:<strong>Intel汇编和AT&T汇编</strong>,DOS、Windows使用Intel汇编,而Unix、Linux、MacOS使用AT&T汇编。</p><h4 id="Intel和AT-amp-T汇编格式的区别"><a href="#Intel和AT-amp-T汇编格式的区别" class="headerlink" title="Intel和AT&T汇编格式的区别"></a>Intel和AT&T汇编格式的区别</h4><ol><li>Intel的第一个操作数是目标操作数,第二个操作数是源操作数;AT&T的第一个操作数是源操作数,第二个操作数是目标操作数。</li><li>寄存器的表示:Intel的寄存器直接写寄存器名字(eax);AT&T的寄存器要在前面加一个百分号%(%eax)。</li><li>立即数表示:Intel的立即数前不用加任何标志(1);AT&T的立即数前要加<code>$</code>符号修饰($1)。</li><li>括号的使用:Intel中寻址时用的括号是中括号<code>[]</code>;AT&T中使用的是小括号<code>()</code>。</li></ol><h4 id="X64寄存器表-部分"><a href="#X64寄存器表-部分" class="headerlink" title="X64寄存器表(部分)"></a>X64寄存器表(部分)</h4><p><img src="https://akboom20.github.io/Images/BombLab/register.jpg"></p><ul><li><p>每个寄存器的用途并不是单一的。</p></li><li><p>%rax 通常用于存储函数调用的返回结果,同时也用于乘法和除法指令中。在imul 指令中,两个64位的乘法最多会产生128位的结果,需要 %rax 与 %rdx 共同存储乘法结果,在div 指令中被除数是128 位的,同样需要%rax 与 %rdx 共同存储被除数。</p></li><li><p>%rsp 是堆栈指针寄存器,通常会指向栈顶位置,堆栈的 pop 和push 操作就是通过改变 %rsp 的值即移动堆栈指针的位置来实现的。</p></li><li><p>%rbp 是栈帧指针,用于标识当前栈帧的起始位置</p></li><li><p>%rdi, %rsi, %rdx, %rcx,%r8, %r9 六个寄存器用于存储函数调用时的6个参数(如果有6个或6个以上参数的话)。</p></li><li><p>被标识为 “miscellaneous registers” 的寄存器,属于通用性更为广泛的寄存器,编译器或汇编程序可以根据需要存储任何数据。</p></li></ul><h3 id="Phase-1-字符串比较"><a href="#Phase-1-字符串比较" class="headerlink" title="Phase_1 字符串比较"></a>Phase_1 字符串比较</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs assembly">0000000000400ef0 <phase_1>:<br> 400ef0:48 83 ec 08 sub $0x8,%rsp ; 开启8个字节的栈空间<br> 400ef4:be 80 25 40 00 mov $0x402580,%esi ; 将地址处的值放到esi寄存器<br> 400ef9:e8 80 04 00 00 callq 40137e <strings_not_equal> ; 调用函数<br> 400efe:85 c0 test %eax,%eax <br> 400f00:74 05 je 400f07 <phase_1+0x17> ; 如果eax寄存器为0则跳过炸弹<br> 400f02:e8 22 07 00 00 callq 401629 <explode_bomb> <br> 400f07:48 83 c4 08 add $0x8,%rsp ; 回收栈空间<br> 400f0b:c3 retq <br></code></pre></td></tr></table></figure><p>显然,<code>explode_bomb</code>函数应该是炸弹爆炸函数,调用了这个函数炸弹就爆炸了。我们的目标是跳过这个函数。所以400f00跳转指令必须要执行,那么eax寄存器的值得是0,eax寄存器就是返回值寄存器,进一步<code>strings_not_equal</code>函数的返回值必须是0。再看看<code>strings_not_equal</code>函数,发现字符串相等返回0,否则返回1。phase_1需要我们传入一个参数,这个参数就是第一个参数,存放在rdi寄存器,接着被传入到<code>strings_not_equal</code>函数,两个参数指向的字符串进行比较。所以我们输入一个与0x402580地址处相同的字符串就可以跳过爆炸。</p><p>使用gdb的 examine命令(缩写为x)可以查看内存单元:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs shell">x/<n/f/u> <addr><br><br>n:是正整数,表示需要显示的内存单元的个数,即从当前地址向后显示n个内存单元的内容,<br>一个内存单元的大小由第三个参数u定义。<br><br>f:表示addr指向的内存内容的输出格式,s对应输出字符串,此处需特别注意输出整型数据的格式:<br> x 按十六进制格式显示变量.<br> d 按十进制格式显示变量。<br> u 按十进制格式显示无符号整型。<br> o 按八进制格式显示变量。<br> t 按二进制格式显示变量。<br> a 按十六进制格式显示变量。<br> c 按字符格式显示变量。<br> f 按浮点数格式显示变量。<br><br>u:就是指以多少个字节作为一个内存单元-unit,默认为4。u还可以用被一些字符表示:<br> 如b=1 byte, h=2 bytes,w=4 bytes,g=8 bytes.<br><br><addr>:表示内存地址<br></code></pre></td></tr></table></figure><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs shell"><span class="hljs-meta prompt_"># </span><span class="language-bash">首先gdb bomb 进入调试模式</span><br>gdb bomb<br><span class="hljs-meta prompt_"># </span><span class="language-bash">然后查看地址处的字符串</span><br>x/s 0x402580 # 显示0x402580地址开始的以'\0'结束的字符串<br><span class="hljs-meta prompt_"># </span><span class="language-bash">结果</span><br>"For NASA, space is still a high priority."<br></code></pre></td></tr></table></figure><p>拿到了esi处的字符串,就得到了我们要输入的答案了。</p><p>接着附上phase_1的c语言代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">phase1</span><span class="hljs-params">(<span class="hljs-type">char</span> * input)</span><br>{<br> <span class="hljs-keyword">if</span>(strings_not_equal(input,<span class="hljs-string">"For NASA, space is still a high priority."</span>)) explode_bomb();<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span>;<br>}<br></code></pre></td></tr></table></figure><h3 id="Phase-2-循环"><a href="#Phase-2-循环" class="headerlink" title="Phase_2 循环"></a>Phase_2 循环</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs assembly">0000000000400f0c <phase_2>:<br> 400f0c:55 push %rbp ; rbp是帧指针 保存调用者的帧地址<br> 400f0d:53 push %rbx<br> 400f0e:48 83 ec 28 sub $0x28,%rsp ; 开辟40个字节的栈空间<br> 400f12:48 89 e6 mov %rsp,%rsi ; 栈指针赋给rsi寄存器<br> 400f15:e8 45 07 00 00 callq 40165f <read_six_numbers>; 调用函数<br> 400f1a:83 3c 24 00 cmpl $0x0,(%rsp) <br> 400f1e:79 24 jns 400f44 <phase_2+0x38> ; rsp指向的值等于0则跳过炸弹<br> 400f20:e8 04 07 00 00 callq 401629 <explode_bomb><br><br> 400f25:eb 1d jmp 400f44 <phase_2+0x38> ; 直接跳转<br>##################################################################################################<br> 400f27:89 d8 mov %ebx,%eax ; ebx 寄存器的值赋给eax<br> 400f29:03 45 fc add -0x4(%rbp),%eax ; eax = eax + (rbp -4)指向的值<br> 400f2c:39 45 00 cmp %eax,0x0(%rbp) <br> 400f2f:74 05 je 400f36 <phase_2+0x2a> ; eax 等于 rbp指向的值则跳过炸弹<br> 400f31:e8 f3 06 00 00 callq 401629 <explode_bomb><br> 400f36:83 c3 01 add $0x1,%ebx ; ebx + 1<br> 400f39:48 83 c5 04 add $0x4,%rbp ; rbx + 4<br> 400f3d:83 fb 06 cmp $0x6,%ebx <br> 400f40:75 e5 jne 400f27 <phase_2+0x1b> ; ebx 不等于6则跳转<br>###################################################################################################<br> 400f42:eb 0c jmp 400f50 <phase_2+0x44><br> <br> 400f44:48 8d 6c 24 04 lea 0x4(%rsp),%rbp ; rsp+4地址赋给rbp<br> 400f49:bb 01 00 00 00 mov $0x1,%ebx ; rbx初始化为1<br> 400f4e:eb d7 jmp 400f27 <phase_2+0x1b> ; 直接跳转<br> <br> 400f50:48 83 c4 28 add $0x28,%rsp ; 回收栈空间<br> 400f54:5b pop %rbx<br> 400f55:5d pop %rbp<br> 400f56:c3 retq <br></code></pre></td></tr></table></figure><p>汇编代码有一点长, 分部分看更清晰。第一部分调用了<code>read_six_numbers</code>,传了2个参数,一个是我们要输入的第一个参数<code>rdi</code>,地址<code>rsp</code>作为第二个参数<code>rsi</code>。字面意思读6个数,直接看看代码:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs assembly">000000000040165f <read_six_numbers>:<br> 40165f:48 83 ec 18 sub $0x18,%rsp ; 开辟24个字节栈空间<br> 401663:48 89 f2 mov %rsi,%rdx ; rsi参数放到rdx寄存器<br> 401666:48 8d 4e 04 lea 0x4(%rsi),%rcx ; rsi+4地址放到rcx寄存器<br> 40166a:48 8d 46 14 lea 0x14(%rsi),%rax <br> 40166e:48 89 44 24 08 mov %rax,0x8(%rsp) ; rsi+20地址放到rsp+8地址处<br> 401673:48 8d 46 10 lea 0x10(%rsi),%rax <br> 401677:48 89 04 24 mov %rax,(%rsp) ; rsi+16地址放到rsp地址处<br> 40167b:4c 8d 4e 0c lea 0xc(%rsi),%r9 ; rsi+12地址放到r9处<br> 40167f:4c 8d 46 08 lea 0x8(%rsi),%r8 ; rsi+8地址放到r8处<br> 401683:be 75 28 40 00 mov $0x402875,%esi ; 地址处的值放到esi中<br> 401688:b8 00 00 00 00 mov $0x0,%eax ; eax 设置为0<br> 40168d:e8 9e f5 ff ff callq 400c30 <__isoc99_sscanf@plt>; 调用函数<br> 401692:83 f8 05 cmp $0x5,%eax ; eax大于5则跳过炸弹<br> 401695:7f 05 jg 40169c <read_six_numbers+0x3d><br> 401697:e8 8d ff ff ff callq 401629 <explode_bomb><br> 40169c:48 83 c4 18 add $0x18,%rsp ;回收栈空间<br> 4016a0:c3 retq <br></code></pre></td></tr></table></figure><p><code>read_six_numbers</code>一开始进行了一系列寄存器操作,然后调用<code>__isoc99_sscanf</code>函数。</p><p><code>rdi</code>寄存器作为第一个参数,被传入到<code>__isoc99_sscanf</code>里面。<code>__isoc99_sscanf</code>的第二个参数呢,在地址<code>0x402875</code>处,查看一下,发现是这一串字符串:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">x/s 0x402875<br>"%d %d %d %d %d %d" <br></code></pre></td></tr></table></figure><p>意思就是<code>__isoc99_sscanf</code>函数从它的第一个参数里面读取数字了,第三、四、五、六个参数分别在<code>rdx,rcx,r8,r9</code>中,另外还多用了2个栈空间作为第七个和第八个参数。此时的栈空间有点复杂,可以画个图方便理解:</p><p><img src="https://akboom20.github.io/Images/BombLab/readsixnumbers.png"></p><p>可以猜想,<code>__isoc99_sscanf</code>函数的第3-8个函数读取了我们输入的第一个参数,获得了6个数的值,然后把他们依次放入到了<code>rsi rsi+4 rsi+8 rsi+12 rsi+16 rsi+20</code>这些地址指向的栈空间当中,可以看作是一个数组。然后要求函数的返回值大于5,这个返回值应该是获得的数据个数了,我们至少得输入6个数据,多输的数据不会被处理。</p><p>再看phase2函数,第一个炸弹要求<code>rsp</code>指向的值等于0,所以显然输入的第一个数必须是0。然后跳转到<code>400f44</code>地址处,<code>rsp</code>指针加上4的地址赋给rbp,rbp现在指向第二个数了,然后将ebx寄存器赋值1,接着跳转到一个循环当中:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs assembly">LOOP:<br>400f27:89 d8 mov %ebx,%eax ; ebx 寄存器的值赋给eax<br>400f29:03 45 fc add -0x4(%rbp),%eax ; eax = eax + (rbp -4)指向的值<br>400f2c:39 45 00 cmp %eax,0x0(%rbp) <br>400f2f:74 05 je 400f36 <phase_2+0x2a> ; eax 等于 rbp指向的值则跳过炸弹<br>400f31:e8 f3 06 00 00 callq 401629 <explode_bomb><br>400f36:83 c3 01 add $0x1,%ebx ; ebx + 1<br>400f39:48 83 c5 04 add $0x4,%rbp ; rbx + 4<br>400f3d:83 fb 06 cmp $0x6,%ebx <br>400f40:75 e5 jne 400f27 <phase_2+0x1b> ; ebx 不等于6则跳转<br></code></pre></td></tr></table></figure><p>这个循环还是很容易看懂的,很明显是一个do while循环。eax一开始等于1,接着1+(rbp-4)要等于(rbp),意思是第2个数要比第1个数大1。然后就要注意了,可能有人直接就不看后面了,想当然的认为那这6个数不就是个0初项,1公差的等差数列,答案是“0 1 2 3 4 5 6”,没错,我就在这里炸了一次。注意后面ebx每次循环都被+1,所以第3个数比第2个数大2,第4个数比第3个数大3,依次类推,最后ebx加到6,循环执行了5次,得到后面5个数。</p><p>最后6个数就是 “0 1 3 6 10 15”。</p><p>同样,我尝试将汇编翻译成C代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">phase2</span><span class="hljs-params">(<span class="hljs-type">char</span> * input)</span><br>{<br> <span class="hljs-type">int</span> * addr; <span class="hljs-comment">//也可以写成数组 为了更接近汇编写成了地址</span><br> read_six_numbers(input,addr);<br> <span class="hljs-keyword">if</span>(*addr == <span class="hljs-number">0</span>)<br> {<br> <span class="hljs-type">int</span> i = <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">do</span><br> {<br> <span class="hljs-keyword">if</span>(*(addr+<span class="hljs-number">4</span>) == *addr + i){<br> addr+=<span class="hljs-number">4</span>;<br> i++;<br> }<br> <span class="hljs-keyword">else</span> explode_bomb();<br> }<span class="hljs-keyword">while</span>(i != <span class="hljs-number">6</span>)<br> }<br> <span class="hljs-keyword">else</span> explode_bomb<br><span class="hljs-keyword">return</span> ;<br>}<br></code></pre></td></tr></table></figure><h3 id="Phase-3-条件-x2F-分支"><a href="#Phase-3-条件-x2F-分支" class="headerlink" title="Phase_3 条件/分支"></a>Phase_3 条件/分支</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br></pre></td><td class="code"><pre><code class="hljs assembly">0000000000400f57 <phase_3>:<br> 400f57:48 83 ec 18 sub $0x18,%rsp ; 开辟24个字节的栈空间<br> 400f5b:4c 8d 44 24 08 lea 0x8(%rsp),%r8 ; 第5个参数<br> 400f60:48 8d 4c 24 07 lea 0x7(%rsp),%rcx ; 第4个参数<br> 400f65:48 8d 54 24 0c lea 0xc(%rsp),%rdx ; 第3个参数<br> 400f6a:be d6 25 40 00 mov $0x4025d6,%esi ; 第2个参数<br> 400f6f:b8 00 00 00 00 mov $0x0,%eax <br> 400f74:e8 b7 fc ff ff callq 400c30 <__isoc99_sscanf@plt><br> 400f79:83 f8 02 cmp $0x2,%eax; 返回值大于2则跳过炸弹<br> 400f7c:7f 05 jg 400f83 <phase_3+0x2c><br> 400f7e:e8 a6 06 00 00 callq 401629 <explode_bomb><br> <br> 400f83:83 7c 24 0c 07 cmpl $0x7,0xc(%rsp)<br> 400f88:0f 87 f9 00 00 00 ja 401087 <phase_3+0x130> ; 第一个数要<=7 大于7则跳转爆炸 同时无符号 不能是负数<br> <br> 400f8e:8b 44 24 0c mov 0xc(%rsp),%eax ; eax被赋值第一个数 <br> 400f92:ff 24 c5 e0 25 40 00 jmpq *0x4025e0(,%rax,8)<br> ###################################################################################################<br> ; rax = 0<br> 400f99:b8 7a 00 00 00 mov $0x7a,%eax ; eax 被赋值122 <br> 400f9e:83 7c 24 08 65 cmpl $0x65,0x8(%rsp) <br> 400fa3:0f 84 e8 00 00 00 je 401091 <phase_3+0x13a> ; 第3个数等于101 则跳出case 同时跳过炸弹<br> 400fa9:e8 7b 06 00 00 callq 401629 <explode_bomb> <br> 400fae:b8 7a 00 00 00 mov $0x7a,%eax ; eax 被赋值122 <br> 400fb3:e9 d9 00 00 00 jmpq 401091 <phase_3+0x13a> ; 跳出case<br> ###################################################################################################<br> ; rax = 1<br> 400fb8:b8 51 00 00 00 mov $0x51,%eax ; eax 被赋值81<br> 400fbd:81 7c 24 08 5a 03 00 cmpl $0x35a,0x8(%rsp) <br> 400fc4:00 <br> 400fc5:0f 84 c6 00 00 00 je 401091 <phase_3+0x13a> ; 第三个数等于858 则跳出case 同时跳过炸弹<br> 400fcb:e8 59 06 00 00 callq 401629 <explode_bomb> <br> 400fd0:b8 51 00 00 00 mov $0x51,%eax <br> 400fd5:e9 b7 00 00 00 jmpq 401091 <phase_3+0x13a> <br> ###################################################################################################<br> ; rax = 2<br> 400fda:b8 53 00 00 00 mov $0x53,%eax ; eax被赋值83<br> 400fdf:81 7c 24 08 a9 01 00 cmpl $0x1a9,0x8(%rsp)<br> 400fe6:00 <br> 400fe7:0f 84 a4 00 00 00 je 401091 <phase_3+0x13a>; 第三个数等于425 则跳出case 同时跳过炸弹<br> 400fed:e8 37 06 00 00 callq 401629 <explode_bomb><br> 400ff2:b8 53 00 00 00 mov $0x53,%eax<br> 400ff7:e9 95 00 00 00 jmpq 401091 <phase_3+0x13a><br> ###################################################################################################<br> ; rax = 3<br> 400ffc:b8 44 00 00 00 mov $0x44,%eax ; eax被赋值68<br> 401001:81 7c 24 08 44 02 00 cmpl $0x244,0x8(%rsp)<br> 401008:00 <br> 401009:0f 84 82 00 00 00 je 401091 <phase_3+0x13a> ; 第三个数等于580 则跳出case 同时跳过炸弹<br> 40100f:e8 15 06 00 00 callq 401629 <explode_bomb><br> 401014:b8 44 00 00 00 mov $0x44,%eax<br> 401019:eb 76 jmp 401091 <phase_3+0x13a><br> ###################################################################################################<br> ; rax = 4<br> 40101b:b8 4a 00 00 00 mov $0x4a,%eax ; eax被赋值74<br> 401020:81 7c 24 08 81 02 00 cmpl $0x281,0x8(%rsp)<br> 401027:00 <br> 401028:74 67 je 401091 <phase_3+0x13a> ; 第三个数等于641 则跳出case 同时跳过炸弹<br> 40102a:e8 fa 05 00 00 callq 401629 <explode_bomb><br> 40102f:b8 4a 00 00 00 mov $0x4a,%eax<br> 401034:eb 5b jmp 401091 <phase_3+0x13a><br> ###################################################################################################<br> ; rax = 5<br> 401036:b8 4d 00 00 00 mov $0x4d,%eax ; eax被赋值77<br> 40103b:81 7c 24 08 c7 01 00 cmpl $0x1c7,0x8(%rsp)<br> 401042:00 <br> 401043:74 4c je 401091 <phase_3+0x13a>; 第三个数等于455 则跳出case 同时跳过炸弹<br> 401045:e8 df 05 00 00 callq 401629 <explode_bomb><br> 40104a:b8 4d 00 00 00 mov $0x4d,%eax<br> 40104f:eb 40 jmp 401091 <phase_3+0x13a><br>####################################################################################################<br>; rax = 6<br> 401051:b8 54 00 00 00 mov $0x54,%eax ; eax被赋值84 <br> 401056:81 7c 24 08 90 00 00 cmpl $0x90,0x8(%rsp)<br> 40105d:00 <br> 40105e:74 31 je 401091 <phase_3+0x13a>; 第三个数等于144 则跳出case 同时跳过炸弹<br> 401060:e8 c4 05 00 00 callq 401629 <explode_bomb><br> 401065:b8 54 00 00 00 mov $0x54,%eax<br> 40106a:eb 25 jmp 401091 <phase_3+0x13a><br> #################################################################################################<br> ; rax = 7<br> 40106c:b8 61 00 00 00 mov $0x61,%eax ; eax被赋值97<br> 401071:81 7c 24 08 4b 03 00 cmpl $0x34b,0x8(%rsp)<br> 401078:00 <br> 401079:74 16 je 401091 <phase_3+0x13a>; 第三个数等于843 则跳出case 同时跳过炸弹<br> 40107b:e8 a9 05 00 00 callq 401629 <explode_bomb><br> 401080:b8 61 00 00 00 mov $0x61,%eax<br> 401085:eb 0a jmp 401091 <phase_3+0x13a><br> 401087:e8 9d 05 00 00 callq 401629 <explode_bomb><br> 40108c:b8 63 00 00 00 mov $0x63,%eax<br>###################################################################################################<br> 401091:3a 44 24 07 cmp 0x7(%rsp),%al ; 第2个数等于eax寄存器的低八位对应的字符<br> 401095:74 05 je 40109c <phase_3+0x145><br> 401097:e8 8d 05 00 00 callq 401629 <explode_bomb><br> 40109c:48 83 c4 18 add $0x18,%rsp<br> 4010a0:c3 retq <br></code></pre></td></tr></table></figure><p>phase3的代码乍一看有点长,实际上仔细看看并不复杂,中间一段都是差不多的结构,因为这个phase叫做条件/分支,显然是一个switch结构。代码还是分部分来看。</p><p>开头又是调用了<code>__isoc99_sscanf</code>函数,这个函数我们应该很熟悉了,它通过从第一个参数中读取数据,赋值给后面参数指向的地址,第二个参数就是读取的占位字符串,看看地址<code>0x4025d6</code>处的内容:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">x/s 0x4025d6<br>"%d %c %d"<br></code></pre></td></tr></table></figure><p>这是要我们输入两个数字,一个字符,分别赋给后面的第3、4、5个参数指向的地址。所以容易知道执行完<code>__isoc99_sscanf</code>,栈空间的<code>0xc(%rsp)</code>地址保存的是第1个数字,<code>0x7(%rsp)</code>地址保存的是第2个字符,<code>0x8(%rsp)</code>地址保存的是第3个数字。然后要求返回值大于2才能跳过炸弹,我们得输入3个数据。</p><p>接着进行一个简单的判断,第一个数在[0,7]这个范围内,然后出现了一条特别的指令:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs assembly">400f92:ff 24 c5 e0 25 40 00 jmpq *0x4025e0(,%rax,8)<br></code></pre></td></tr></table></figure><p>这个指令根据一个内存基地址进行跳转,明显这是一个switch结构,根据<code>rax</code>的值,决定跳转到哪一个case执行。</p><p>既然跳转表就在下方,不出意外,地址<code>0x4025e0</code>中保存的值应该也就是下一条指令的值。</p><p>使用gdb查看这个地址中的值,它应该是一个地址,所以使用16进制,显示8个字节作为一个内存单元查看:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">x/xg 0x4025e0<br>0x0000000000400f99<br></code></pre></td></tr></table></figure><p>发现得到的地址就是该指令的下一条地址。可能有人会问为什么地址中保存一个地址呢,因为这样可以让跳转表在任何其他地址,如果直接写成<code>0x400f99</code>,那么跳转表的地址就被写死了。</p><p>接下来只需要结合基地址和<code>rax</code>的值计算出跳转地址即可,答案肯定是不止一个的,想直接得到答案的话取<code>rax</code>等于0就行了,我这里查看出所有能跳转的地址,给出所有答案。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs shell">(gdb) x/8xg 0x4025e0 # 从0x4025e0开始向后查看8个内存单元 16进制显示 一个内存单元8个字节<br>0x4025e0:0x0000000000400f990x0000000000400fb8<br>0x4025f0:0x0000000000400fda0x0000000000400ffc<br>0x402600:0x000000000040101b0x0000000000401036<br>0x402610:0x00000000004010510x000000000040106c<br></code></pre></td></tr></table></figure><p>查出来跳转表有8个地址。分别对应case为0~7的情况。</p><p>以<code>rax</code>等于0为例,那么第1个数就为0,进入case0,<code>eax</code>被赋值为122,第3个数必须等于101,跳出switch,第2个数等于eax寄存器的低八位,注意第2个数是会被当成“%c”也就是字符解释,字符存储的是8位的ASCII码,所以查表,找到ascii码为122对应的字符,是小写的“z”。所以得到了一组结果“0 z 101”。</p><p>所有的结果是:</p><figure class="highlight 1c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs 1c"><span class="hljs-string">"0 z 101"</span><br><span class="hljs-string">"1 Q 858"</span><br><span class="hljs-string">"2 S 425"</span><br><span class="hljs-string">"3 D 580"</span><br><span class="hljs-string">"4 J 641"</span><br><span class="hljs-string">"5 M 455"</span><br><span class="hljs-string">"6 T 144"</span><br><span class="hljs-string">"7 a 843"</span><br></code></pre></td></tr></table></figure><p>因为此题的case有点多,加上每个case之后有一些对炸弹拆除没作用的代码,所以就不翻译成C代码了,不过总体还是很简单的。</p><h3 id="Phase-4-递归调用和栈"><a href="#Phase-4-递归调用和栈" class="headerlink" title="Phase_4 递归调用和栈"></a>Phase_4 递归调用和栈</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs assembly">00000000004010d4 <phase_4>:<br> 4010d4:48 83 ec 18 sub $0x18,%rsp<br> 4010d8:48 8d 4c 24 08 lea 0x8(%rsp),%rcx ; 第4个参数<br> 4010dd:48 8d 54 24 0c lea 0xc(%rsp),%rdx ; 第3个参数<br> 4010e2:be 81 28 40 00 mov $0x402881,%esi <br> 4010e7:b8 00 00 00 00 mov $0x0,%eax<br> 4010ec:e8 3f fb ff ff callq 400c30 <__isoc99_sscanf@plt><br> 4010f1:83 f8 02 cmp $0x2,%eax ; eax不等于2则跳转爆炸<br> 4010f4:75 07 jne 4010fd <phase_4+0x29><br> 4010f6:83 7c 24 0c 0e cmpl $0xe,0xc(%rsp)<br> 4010fb:76 05 jbe 401102 <phase_4+0x2e> ; 第1个数必须小于等于14 跳过炸弹<br> 4010fd:e8 27 05 00 00 callq 401629 <explode_bomb><br> 401102:ba 0e 00 00 00 mov $0xe,%edx; func4的e第3个参数 初值14<br> 401107:be 00 00 00 00 mov $0x0,%esi ; func4的第2个参数 初值0<br> 40110c:8b 7c 24 0c mov 0xc(%rsp),%edi ; func4的第 1个参数 也是要输入的第1个数 <br> 401110:e8 8c ff ff ff callq 4010a1 <func4> ; 调用func4<br> 401115:83 f8 1f cmp $0x1f,%eax<br> 401118:75 07 jne 401121 <phase_4+0x4d> ; 返回值必须等于31 那么第1个参数必须是?<br> 40111a:83 7c 24 08 1f cmpl $0x1f,0x8(%rsp)<br> 40111f:74 05 je 401126 <phase_4+0x52> ; 第二个参数必须等于31 跳过炸弹<br> 401121:e8 03 05 00 00 callq 401629 <explode_bomb><br> 401126:48 83 c4 18 add $0x18,%rsp<br> 40112a:c3 retq <br></code></pre></td></tr></table></figure><p>phase4的代码很简短,意思也很容易看懂,问题的关键在于<code>func4</code>这个函数,需要让这个函数返回31,从而确定我们要输入的第1个数。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs assembly">00000000004010a1 <func4>:<br> 4010a1:53 push %rbx <br> 4010a2:89 d0 mov %edx,%eax <br> 4010a4:29 f0 sub %esi,%eax<br> 4010a6:89 c3 mov %eax,%ebx <br> 4010a8:c1 eb 1f shr $0x1f,%ebx <br> 4010ab:01 d8 add %ebx,%eax<br> 4010ad:d1 f8 sar %eax<br> 4010af:8d 1c 30 lea (%rax,%rsi,1),%ebx <br> 4010b2:39 fb cmp %edi,%ebx <br> 4010b4:7e 0c jle 4010c2 <func4+0x21> <br> 4010b6:8d 53 ff lea -0x1(%rbx),%edx<br> 4010b9:e8 e3 ff ff ff callq 4010a1 <func4><br> 4010be:01 d8 add %ebx,%eax<br> 4010c0:eb 10 jmp 4010d2 <func4+0x31> <br> 4010c2:89 d8 mov %ebx,%eax <br> 4010c4:39 fb cmp %edi,%ebx <br> 4010c6:7d 0a jge 4010d2 <func4+0x31> <br> 4010c8:8d 73 01 lea 0x1(%rbx),%esi <br> 4010cb:e8 d1 ff ff ff callq 4010a1 <func4> <br> 4010d0:01 d8 add %ebx,%eax<br> 4010d2:5b pop %rbx<br> 4010d3:c3 retq <br></code></pre></td></tr></table></figure><p><code>func4</code>里面嵌套了递归调用,看递归调用的汇编非常容易犯的错是递归调用之后没有更新<code>eax</code>这个返回值寄存器。<code>func4</code>前面进行了一系列的寄存器赋值操作,不熟练的话还是逐行翻译。</p><p>比如设传过来的三个参数是(x,y,z),对出现了两个新寄存器<code>ebx</code>和<code>eax</code>,再设两个变量k和t。</p><p>前几行,一行一行的翻译成C代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs c">t = z;<br>t = t - y;<br>k = t;<br>k =>> <span class="hljs-number">31</span>;<span class="hljs-comment">//逻辑右移 最后只剩下最高位即符号位</span><br>t = t + k;<br>t =>><span class="hljs-number">1</span>;<br>k = t + y;<br></code></pre></td></tr></table></figure><p>注意<code>shr</code>指令是逻辑右移,高位补0,<code>sar</code>指令是算数右移高位补符号位。</p><p>C/C++中,逻辑和算数移位都是<code>>></code>符号。对于无符号数,可以认为是逻辑左移和逻辑右移。对于有符号数,可以认为是算术左移和算术右移,有符号数执行逻辑右移,可以先将它强制类型转换为无符号类型。无符号数执行算术右移,可以先将它强制类型转换为有符号类型。</p><p>对上面的代码减化一下:</p><figure class="highlight ini"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs ini"><span class="hljs-attr">t</span> = z - y<span class="hljs-comment">;</span><br><span class="hljs-attr">k</span> = (z < y) ? <span class="hljs-number">1</span> : <span class="hljs-number">0</span><br><span class="hljs-attr">t</span> = (t + k)>><span class="hljs-number">1</span><span class="hljs-comment">;</span><br><span class="hljs-attr">k</span> = t + y<span class="hljs-comment">;</span><br></code></pre></td></tr></table></figure><p>接下来就是if判断了,翻译成C代码也很容易。唯一注意的点还是注意递归调用之后返回寄存器的值记得更新。</p><p>完整的C代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-comment">//x: %edi </span><br><span class="hljs-comment">//y: %esi </span><br><span class="hljs-comment">//z: %edx </span><br><span class="hljs-comment">//k: %ebx </span><br><span class="hljs-comment">//t: %eax</span><br><span class="hljs-type">int</span> <span class="hljs-title function_">func4</span><span class="hljs-params">(<span class="hljs-type">int</span> x, <span class="hljs-type">int</span> y, <span class="hljs-type">int</span> z)</span><br>{<br> <span class="hljs-comment">//y的初始值为0,z的初始值为14 得到x = 13时返回31</span><br> <span class="hljs-type">int</span> t = z - y;<br> <span class="hljs-type">int</span> k = z < y ? <span class="hljs-number">1</span> : <span class="hljs-number">0</span>;<span class="hljs-comment">//此处应该是逻辑右移31位 高位全部补0 只取符号位</span><br> t = (t + k)>><span class="hljs-number">1</span>;<br> k = t + y;<br> <span class="hljs-keyword">if</span> (k > x)<span class="hljs-comment">//不跳转</span><br> {<br> z = k - <span class="hljs-number">1</span>;<br><span class="hljs-keyword">return</span> func4(x, y, z)+k;<br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-comment">//跳转</span><br> {<br> t = k;<br> <span class="hljs-keyword">if</span> (k < x)<span class="hljs-comment">//不跳转</span><br> {<br> y = k + <span class="hljs-number">1</span>;<br> <span class="hljs-keyword">return</span> func4(x, y, z)+k;<br> <br> }<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> t;<br> }<br>}<br></code></pre></td></tr></table></figure><p>有了C代码,现在只需要找一个x让递归函数返回31即可,最粗暴的方式直接写一个C程序,调用func函数遍历一下0~100找出返回值为31的x,得到x为13时满足条件。</p><p>所以答案是:“ 13 31”。</p><h3 id="Phase-5-指针"><a href="#Phase-5-指针" class="headerlink" title="Phase_5 指针"></a>Phase_5 指针</h3><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs assembly">000000000040112b <phase_5>:<br> 40112b:53 push %rbx<br> 40112c:48 89 fb mov %rdi,%rbx<br> 40112f:e8 2d 02 00 00 callq 401361 <string_length><br> 401134:83 f8 06 cmp $0x6,%eax<br> 401137:74 05 je 40113e <phase_5+0x13> ; 字符串长度是6则跳过炸弹<br> 401139:e8 eb 04 00 00 callq 401629 <explode_bomb><br> 40113e:b8 00 00 00 00 mov $0x0,%eax; eax = 0<br> 401143:ba 00 00 00 00 mov $0x0,%edx; edx = 0<br> ##################################################################################################<br> 401148:0f b6 0c 03 movzbl (%rbx,%rax,1),%ecx ; 数据传送 0扩展<br> 40114c:83 e1 0f and $0xf,%ecx; 取ecx的低4位<br> 40114f:03 14 8d 20 26 40 00 add 0x402620(,%rcx,4),%edx<br> 401156:48 83 c0 01 add $0x1,%rax; rax +=1<br> 40115a:48 83 f8 06 cmp $0x6,%rax ; rax 不等于6则跳转<br> 40115e:75 e8 jne 401148 <phase_5+0x1d><br> <br> 401160:83 fa 32 cmp $0x32,%edx<br> 401163:74 05 je 40116a <phase_5+0x3f> ; edx必须等于50 偏移量 011124满足条件<br> 401165:e8 bf 04 00 00 callq 401629 <explode_bomb><br> 40116a:5b pop %rbx<br> 40116b:c3 retq <br></code></pre></td></tr></table></figure><p>phase5的代码也比较简短,主体是一个循环结构。第一部分调用了<code>string_length</code>函数,这个函数并不陌生,在<code>strings_not_equal</code>函数中也调用了这个函数,作为两个字符串相等的前提,可以推断这个函数就是返回字符串的长度。我们输入的第一个参数<code>rdi</code>作为<code>string_length</code>函数的参数,字符串长度必须是6才能跳过第一个炸弹。</p><p>接着<code>eax</code>,<code>edx</code>两个寄存器被赋值0。然后进入一个循环结构。</p><p>401148地址处有一个特殊一点的指令<code>movzbl</code>:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs assembly"># 数据传送指令<br>movb ; 传送一字节<br>movw ; 传送两字节(一字)<br>movl ; 传送四字节(双字)<br>movq ; 传送八字节(四字)<br># 零扩展<br>movzbw ; 零扩展的字节传送到字<br>movzbl ; 零扩展的字节传送到双字<br>movzwl ; 零扩展的字传送到双字<br>movzbq ; 零扩展的字节传送到四字<br>movzwq ; 零扩展的字传送到四字<br># 符号扩展<br>movsbw ; 符号扩展的字节传送到字<br>movsbl ; 符号扩展的字节传送到双字<br>movswl ; 符号扩展的字传送到双字<br>movsbq ; 符号扩展的字节传送到四字<br>movswq ; 将符号扩展的字传送到四字<br>movslq ; 符号扩展的双字传送到四字<br></code></pre></td></tr></table></figure><p><code>movzbl (%rbx,%rax,1),%ecx</code>指令将零扩展的字节传送到<code>ecx</code>寄存器,接着做了一个位与操作取<code>ecx</code>的低四位。</p><p>下面从内存基地址<code>0x402620</code>,偏移4*<code>rcx</code>处取数据加到<code>edx</code>寄存器,然后循环执行6次后跳出。最终<code>edx</code>寄存器的值要等于50才能跳过炸弹。</p><p>先使用gdb查看基址<code>0x402620</code>往后的内存单元中存储着什么值,由于偏移是4*<code>rcx</code>,所以4字节为一个内存单元,往后查看20个单元,直接将数据显示为10进制试试:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs shell">(gdb) x/20dw 0x402620<br>0x402620 <array.3162>:21061<br>0x402630 <array.3162+16>:121693<br>0x402640 <array.3162+32>:47145<br>0x402650 <array.3162+48>:1181513<br>0x402660:2032168787194828427118023980561970239776<br></code></pre></td></tr></table></figure><p>可以看到20个单元超出了数组的范围了,数组共有16个单元,将20改成16就可以了:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs shell">(gdb) x/16dw 0x402620<br>0x402620 <array.3162>:21061<br>0x402630 <array.3162+16>:121693<br>0x402640 <array.3162+32>:47145<br>0x402650 <array.3162+48>:1181513<br></code></pre></td></tr></table></figure><p>数组的所有值已经找到了,现在只需要凑出6个数的和是50,然后对应出6个字符即可。注意<code>rdi</code>参数是指向<code>char[]</code>数组的第一个字符,所以每次执行<code>movzbl (%rbx,%rax,1),%ecx</code>相当于依次取字符串的第0,1,2,3,4,5,6位。当然做完与操作之后就只取ascii码的低4位了。</p><p>我取的6个数是2,10,10,10,6,12。所以对应的偏移是0,1,1,1,2,4。现在只需要找出满足ascii码的低四位0,1,2,4的字符就行了。找到“011124”就是满足要求的。答案应该还有很多。输入的时候不能包含空格,因为空格也算一个字符。</p><p>答案:“011124”。</p><p>C代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">void</span> <span class="hljs-title function_">phase5</span><span class="hljs-params">(<span class="hljs-type">char</span> * s)</span><br>{<br> <span class="hljs-type">int</span>[] <span class="hljs-built_in">array</span> = {<span class="hljs-number">2</span>,<span class="hljs-number">10</span>,<span class="hljs-number">6</span>,<span class="hljs-number">1</span>,<span class="hljs-number">12</span>,<span class="hljs-number">16</span>,<span class="hljs-number">9</span>,<span class="hljs-number">3</span>,<span class="hljs-number">4</span>,<span class="hljs-number">7</span>,<span class="hljs-number">14</span>,<span class="hljs-number">5</span>,<span class="hljs-number">11</span>,<span class="hljs-number">8</span>,<span class="hljs-number">15</span>,<span class="hljs-number">13</span>}<br> <span class="hljs-type">char</span> * addr = s;<br> <span class="hljs-keyword">if</span>(string_length(s)==<span class="hljs-number">6</span>)<span class="hljs-comment">//字符串长度是6 继续执行</span><br> {<br> <span class="hljs-type">int</span> i = <span class="hljs-number">0</span>; <span class="hljs-type">int</span> sum = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">do</span>{<br> <span class="hljs-type">int</span> offset = (addr + i)&<span class="hljs-number">0xf</span>;<br> sum += <span class="hljs-built_in">array</span>[offset];<br> i++;<br> }<span class="hljs-keyword">while</span>(i!=<span class="hljs-number">6</span>)<br> <span class="hljs-keyword">if</span>(sum == <span class="hljs-number">50</span>) <span class="hljs-keyword">return</span>;<br> <span class="hljs-keyword">else</span> explode_bomb();<br> }<br> <span class="hljs-keyword">else</span> explode_bomb();<br>}<br></code></pre></td></tr></table></figure><h3 id="Phase-6-链表-x2F-指针-x2F-结构"><a href="#Phase-6-链表-x2F-指针-x2F-结构" class="headerlink" title="Phase_6 链表/指针/结构"></a>Phase_6 链表/指针/结构</h3><p>phase6非常的长,而且代码逻辑也比较复杂,需要很有耐心才能看明白,但是任何长代码都是分部分的,一部分一部分的看就会很清晰。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs assembly">40116c:41 55 push %r13<br>40116e:41 54 push %r12<br>401170:55 push %rbp<br>401171:53 push %rbx ; 保存现有的寄存器值<br>401172:48 83 ec 58 sub $0x58,%rsp ; 开辟88个字节空间<br>401176:48 8d 74 24 30 lea 0x30(%rsp),%rsi ; rsi设置为 rsp + 48<br>40117b:e8 df 04 00 00 callq 40165f <read_six_numbers> ; 调用函数 <br>401180:4c 8d 6c 24 30 lea 0x30(%rsp),%r13 ; r13 设置为 rsp + 48<br>401185:41 bc 00 00 00 00 mov $0x0,%r12d ; r12置为0<br></code></pre></td></tr></table></figure><p>开头的<code>read_six_numbers</code>函数非常熟悉,在phase2就使用过,函数传入2个参数,<code>rdi</code>是要输入的6个数字组成的字符串,<code>rsi</code>是存放6个数字的栈空间的基地址。在函数内部会的调用<code>__isoc99_sscanf</code>函数,解析输入的字符串,然后将6个数字分别存到<code>rsi</code>开始的栈空间当中。执行完第一部分,<code>rsp+48 rsp+52 rsp+56 rsp+60 rsp+64 rsp+68 rsp+72</code>这六个地址就存放了输入的六个整数。至于为什么编译器会分配40-24个字节的空间,应该是出于其他考虑。</p><p>接着<code>r13</code>寄存器被赋值<code>rsp+48</code>,<code>r12d</code>寄存器(<code>r12</code>寄存器的低32位)被赋值0,进入下一部分:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><code class="hljs assembly">LOOP:<br> 40118b:4c 89 ed mov %r13,%rbp ; rbp设置为 r13<br> 40118e:41 8b 45 00 mov 0x0(%r13),%eax ; eax设置为 r13处的值<br> 401192:83 e8 01 sub $0x1,%eax ; eax - 1<=5 即eax要<=6 <br> 401195:83 f8 05 cmp $0x5,%eax<br> 401198:76 05 jbe 40119f <phase_6+0x33><br> 40119a:e8 8a 04 00 00 callq 401629 <explode_bomb><br> <br> 40119f:41 83 c4 01 add $0x1,%r12d ; r12 = r12+1<br> 4011a3:41 83 fc 06 cmp $0x6,%r12d ; r12 不等于6则跳转<br> 4011a7:75 07 jne 4011b0 <phase_6+0x44><br> 4011a9:be 00 00 00 00 mov $0x0,%esi ; 当r12加到6时 esi被设置为 0 <br> 4011ae:eb 42 jmp 4011f2 <phase_6+0x86> ; 远跳转<br> <br> 4011b0:44 89 e3 mov %r12d,%ebx ; 将r12复制到ebx<br>LOOP:<br> 4011b3:48 63 c3 movslq %ebx,%rax ; ebx 符号扩展复制到 rax<br> 4011b6:8b 44 84 30 mov 0x30(%rsp,%rax,4),%eax; 第rax个输入数存到eax中<br> 4011ba:39 45 00 cmp %eax,0x0(%rbp) ;比较rbp处的数和 第rax个输入数 相等就爆炸<br> 4011bd:75 05 jne 4011c4 <phase_6+0x58><br> 4011bf:e8 65 04 00 00 callq 401629 <explode_bomb><br> <br> <br> 4011c4:83 c3 01 add $0x1,%ebx; ebx + 1<br> 4011c7:83 fb 05 cmp $0x5,%ebx; ebx 小于等于5则循环<br> 4011ca:7e e7 jle 4011b3 <phase_6+0x47><br> <br> 4011cc:49 83 c5 04 add $0x4,%r13; r13指向下一个数<br> 4011d0:eb b9 jmp 40118b <phase_6+0x1f>; 循环判断所有数都不相等 并且都小于等于6<br></code></pre></td></tr></table></figure><p>首先<code>rbx</code>寄存器被设置为<code>r13</code>寄存器的值,<code>eax</code>被设置为<code>r13</code>寄存器指向的地址处的值。<code>eax</code>小于等于6则跳过炸弹,所以<code>r13</code>寄存器指向的地址处的值要小于等于6,<code>r13</code>一开始是<code>rsp+48</code>地址,即第一个数要小于等于6。</p><p>接着 <code>r12d</code>自增,不等于6则往下跳转。首先将<code>r12d</code>的值复制到<code>ebx</code>,然后<code>ebx</code>符号扩展到<code>rax</code>。下面的一条<code>mov</code>指令,取输入的6个数中的第<code>rax</code>个数,与<code>rbp</code>处的值比较,不相等则跳过炸弹,<code>ebx</code>自增,小于等于5则循环。退出循环后<code>r13</code>的地址加4,即指向下一个输入数,循环。</p><p>不难看出这是一个双层的循环,外层循环判断每个数都小于等于6,里层循环判断每个数都不能相等。</p><p>当<code>r12d</code>加到6时,将<code>esi</code>设置为0,执行一个远跳转指令跳转到<code>0x4011f2</code>,这时才真正退出整个循环。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br></pre></td><td class="code"><pre><code class="hljs assembly">LOOP:<br>4011d2:48 8b 52 08 mov 0x8(%rdx),%rdx ; 取rdx+8地址处的值赋给rdx<br>4011d6:83 c0 01 add $0x1,%eax ; eax++<br>4011d9:39 c8 cmp %ecx,%eax ; eax自增直到与ecx相等<br>4011db:75 f5 jne 4011d2 <phase_6+0x66><br>4011dd:eb 05 jmp 4011e4 <phase_6+0x78><br>###############################################################################################<br>4011df:ba f0 42 60 00 mov $0x6042f0,%edx ; 首地址赋给edx<br><br>4011e4:48 89 14 74 mov %rdx,(%rsp,%rsi,2) ; rdx地址 给 rsp + rsi*2 <br>4011e8:48 83 c6 04 add $0x4,%rsi ; rsi加上4<br>4011ec:48 83 fe 18 cmp $0x18,%rsi ; rsi是否等于 24 等于则跳转<br>4011f0:74 15 je 401207 <phase_6+0x9b><br> <br><br>4011f2:8b 4c 34 30 mov 0x30(%rsp,%rsi,1),%ecx; ecx 等于 输入的第rsi/4个数的值<br>4011f6:83 f9 01 cmp $0x1,%ecx ; ecx小于等于1则跳转<br>4011f9:7e e4 jle 4011df <phase_6+0x73><br><br>4011fb:b8 01 00 00 00 mov $0x1,%eax ; eax赋初值1<br>401200:ba f0 42 60 00 mov $0x6042f0,%edx ; 地址赋给edx<br>401205:eb cb jmp 4011d2 <phase_6+0x66> ; 跳转到上面<br></code></pre></td></tr></table></figure><p>我们刚才跳转到了<code>0x11f2</code>,先从这里开始看。</p><p><code>ecx</code>被赋值第<code>rsi</code>/4个数的值,如果小于等于1,则跳转到<code>0x4011df</code>,否则将<code>eax</code>赋初值1,<code>edx</code>设置为一个内存地址,然后跳转到这段代码开头。</p><p>查看<code>0x6042f0</code>处是什么数据,由于是赋给32位寄存器,查看时使用16进制表示,32字节为一个内存单元,向后查看24个内存单元试试:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs shell">(gdb) x/24xw 0x6042f0<br>0x6042f0 <node1>:0x000000530x000000010x006043000x00000000 # 83 1<br>0x604300 <node2>:0x0000039e0x000000020x006043100x00000000 # 926 2<br>0x604310 <node3>:0x000002850x000000030x006043200x00000000 # 645 3<br>0x604320 <node4>:0x000002170x000000040x006043300x00000000 # 535 4<br>0x604330 <node5>:0x000001350x000000050x006043400x00000000 # 309 5<br>0x604340 <node6>:0x000000bf0x000000060x000000000x00000000 # 191 6<br></code></pre></td></tr></table></figure><p>发现这个地址处是一个node结构,也就是链表结点,并且一个结点占据了16个字节,最后的8字节是指向下一个结点的地址,前4字节是存储的值,中间4字节是结点的索引。<code>0x6042f0</code>就是链表的首地址了。</p><p>因此假设输入的第一个数是2,不小于1,则<code>eax</code>赋初值1,<code>edx</code>设置为链表首地址,然后跳转到开头。开头的循环执行一次,<code>rdx</code>的值加上8,就是第一个链表结点中存储下一个链表结点地址的位置了。然后跳转到<code>0x4011e4</code>,将<code>rdx</code>的值赋给<code>rsp + rsi*2 </code>这个栈地址处,注意此时<code>rdx</code>的值不是下一个结点的地址(<code>0x604300</code>),而是指向下一个结点地址的地址,即<code>0x6042f8</code>。然后<code>rsi</code>加上4,当加到24时跳出这一部分。否则继续取输入的下一个数,循环执行上面的操作。</p><p>经过这一顿分析,我们大致知道了这一段代码的作用,根据输入的数的值作为索引,找到对应的链表结点,将指向这个链表结点的地址依次存放到<code>rsp rsp+8 rsp+16 rsp+24 rsp+32 rsp+40</code>这几个栈空间当中。方便理解,下面举了一个例子,画出了栈空间的图,再次提醒栈中存放的并不是结点的地址,而是指向结点地址的地址,也就是个指针,首地址除外。</p><p><img src="https://akboom20.github.io/Images/BombLab/phase7.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs assembly">401207:48 8b 1c 24 mov (%rsp),%rbx ; rsp地址指向的值赋给rbx<br>40120b:48 8d 44 24 08 lea 0x8(%rsp),%rax ; rsp+8赋给rax<br>401210:48 8d 74 24 30 lea 0x30(%rsp),%rsi ; rsp+48给rsi <br>401215:48 89 d9 mov %rbx,%rcx ; rbx赋给rcx<br><br>LOOP:<br>401218:48 8b 10 mov (%rax),%rdx ; rax处的值赋给rdx<br>40121b:48 89 51 08 mov %rdx,0x8(%rcx) ; rdx的值赋给 rcx+8rcx+8即rcx指向的节点的末8字节<br>40121f:48 83 c0 08 add $0x8,%rax ; 移动到下一个节点<br>401223:48 39 f0 cmp %rsi,%rax ; 判断6个节点是否遍历完毕<br>401226:74 05 je 40122d <phase_6+0xc1> ; 遍历完毕则跳转<br>401228:48 89 d1 mov %rdx,%rcx ; 下一个节点<br>40122b:eb eb jmp 401218 <phase_6+0xac><br></code></pre></td></tr></table></figure><p>开头的4行进行了一系列赋值,<code>rbx</code>被赋值<code>rsp</code>指向的地址处的值,即结点的地址,而不是指针了。<code>rsp+8</code>赋给<code>rax</code>,<code>rsp+48</code>赋给<code>rsi</code>,<code>rbx</code>又赋给<code>rcx</code>。接着进入一个循环。</p><p><code>rax</code>指向的地址的处的值被赋给<code>rdx</code>,此时<code>rdx</code>也是结点的地址了。然后将<code>rdx</code>赋给<code>rcx+8</code>这个地址处,<code>rcx</code>此时是一个结点的地址,那么<code>rcx+8</code>就是一个结点的最后8字节的地址,也就是指向下一个结点那8个字节。所以,这里进行的实际上是结点的重排,<code>rcx</code>处的结点的下一个结点被修改成<code>rdx</code>地址处的结点。</p><p>然后<code>rax</code>加上8,<code>rcx</code>被赋值<code>rdx</code>,循环指向上面的修改结点操作。</p><p>这个循环执行结束后,链表的结点就按照栈中存储的顺序重新排列了。</p><p>然后进入phase6的最后一个部分:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs assembly">40122d:48 c7 42 08 00 00 00 movq $0x0,0x8(%rdx) ; rdx的下一个node的八个字节全设置为0 此时rdx是rsp+40<br>401234:00 <br>401235:bd 05 00 00 00 mov $0x5,%ebp ;ebp赋值为 5<br>LOOP:<br>40123a:48 8b 43 08 mov 0x8(%rbx),%rax ; rbx一直是rsp指向的地址处的值 即第一个结点的地址 rbx+8得到结点的末八字节 即指向下一个结点的指针地址赋给rax<br>40123e:8b 00 mov (%rax),%eax ; 取rax处的值的低32位<br>401240:39 03 cmp %eax,(%rbx) ; 比较链表节点中首4字节值的大小,前一个节点值必须大于等于后一个才能跳过爆炸<br>401242:7d 05 jge 401249 <phase_6+0xdd><br>401244:e8 e0 03 00 00 callq 401629 <explode_bomb><br>401249:48 8b 5b 08 mov 0x8(%rbx),%rbx ; 将 %rbx 向后移动,指向栈中下一个链表节点的地址<br>40124d:83 ed 01 sub $0x1,%ebp<br>401250:75 e8 jne 40123a <phase_6+0xce>; ebp减 1 一共执行 5次 循环判断链表节点值是不是降序的<br></code></pre></td></tr></table></figure><p>最后一个部分还是对地址和值的操作,逻辑并不复杂,主体就是一个循环,判断重新排列后的链表的后一个结点存储的值是否小于前一个结点存储的值,也就是链表重新组织后必须降序排列。</p><p>根据前面查出来的各节点的值,可以确定,链表重新排列后,顺序应该是“2 3 4 5 6 1”。</p><p>所以输入的六个数,也应该是“2 3 4 5 6 1”,就是最终答案了,可以发现绕了一大圈,最关键的也就是最后一个部分,但是phase6涉及的指针、地址、值的操作还是非常有学习的价值的,确实能加深对汇编语言的理解。</p><h3 id="Secret-Phase-二叉树"><a href="#Secret-Phase-二叉树" class="headerlink" title="Secret_Phase 二叉树"></a>Secret_Phase 二叉树</h3><p>在phase6的最后还有一个secret_phase,这个secet_phase在主函数中没有直接调用,我们可以在文件中搜索一下,哪里调用了secret_phase。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br></pre></td><td class="code"><pre><code class="hljs assembly">00000000004017c7 <phase_defused>:<br> 4017c7:48 83 ec 68 sub $0x68,%rsp<br> 4017cb:bf 01 00 00 00 mov $0x1,%edi<br> 4017d0:e8 35 fd ff ff callq 40150a <send_msg><br> 4017d5:83 3d c0 2f 20 00 06 cmpl $0x6,0x202fc0(%rip) # 60479c <num_input_strings><br> 4017dc:75 6d jne 40184b <phase_defused+0x84> ; 这条指令不能跳转<br> 4017de:4c 8d 44 24 10 lea 0x10(%rsp),%r8<br> 4017e3:48 8d 4c 24 08 lea 0x8(%rsp),%rcx<br> 4017e8:48 8d 54 24 0c lea 0xc(%rsp),%rdx<br> 4017ed:be cb 28 40 00 mov $0x4028cb,%esi<br> 4017f2:bf b0 48 60 00 mov $0x6048b0,%edi<br> 4017f7:b8 00 00 00 00 mov $0x0,%eax<br> 4017fc:e8 2f f4 ff ff callq 400c30 <__isoc99_sscanf@plt><br> 401801:83 f8 03 cmp $0x3,%eax<br> 401804:75 31 jne 401837 <phase_defused+0x70> ; 这条指令不能跳转<br> 401806:be d4 28 40 00 mov $0x4028d4,%esi<br> 40180b:48 8d 7c 24 10 lea 0x10(%rsp),%rdi<br> 401810:e8 69 fb ff ff callq 40137e <strings_not_equal><br> 401815:85 c0 test %eax,%eax<br> 401817:75 1e jne 401837 <phase_defused+0x70> ; 这条指令不能跳转<br> 401819:bf 20 27 40 00 mov $0x402720,%edi<br> 40181e:e8 1d f3 ff ff callq 400b40 <puts@plt><br> 401823:bf 48 27 40 00 mov $0x402748,%edi<br> 401828:e8 13 f3 ff ff callq 400b40 <puts@plt><br> 40182d:b8 00 00 00 00 mov $0x0,%eax<br> 401832:e8 64 fa ff ff callq 40129b <secret_phase><br> 401837:bf 80 27 40 00 mov $0x402780,%edi<br> 40183c:e8 ff f2 ff ff callq 400b40 <puts@plt><br> 401841:bf b0 27 40 00 mov $0x4027b0,%edi<br> 401846:e8 f5 f2 ff ff callq 400b40 <puts@plt><br> 40184b:48 83 c4 68 add $0x68,%rsp<br> 40184f:c3 retq <br></code></pre></td></tr></table></figure><p>找到在<code>phase_defused</code>函数中调用了这个<code>secret_phase</code>。前面已经知道每次成功解除炸弹,都会调用<code>phase_defused</code>,但是具体要什么条件呢。简单分析一下,发现只需要上面的3条跳转指令不跳转就可以了。</p><p>第一处跳转指令调用了<code>send_msg</code>函数,然后比较<code>rip</code>寄存器加上一个偏移处的值为6,应该是判断我们输入了多少个字符串,必须要将前6个phase都通过才能避免跳转。</p><p>第二处跳转指令要求<code>__isoc99_sscanf</code>函数的返回值等于3,这个函数在<code>read_six_numbers</code>中分析过了,返回的是读取到的输入数的个数,在这里它有5个参数,<code>edi</code>和<code>esi</code>都是内存地址处的值,后3个参数显然是用来存储读入的3个数的。查看这两个内存地址处是什么值:</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs assembly">(gdb) x/s 0x6048b0<br>0x6048b0 <input_strings+240>:""<br>(gdb) x/s 0x4028cb<br>0x4028cb:"%d %d %s"<br></code></pre></td></tr></table></figure><p>发现第一个参数显示的是空串,并且提示了<code>input_strings+240</code>,看样子应该是我们输入过的某一个值,第二个参数就很熟悉了,是一串解析字符串的占位符,所以第一个参数应该是2个数字,1个字符串。在phase4我们输入的是2个数字,但是最后的字符串是什么?接着往下看一下。</p><p>第三个跳转指令将<code>0x4028d4</code>地址处的值赋给<code>esi</code>,然后执行<code>strings_not_equal</code>函数判断<code>0x10(%rsp)</code>位置处的值是否和<code>esi</code>的值相等,必须相等跳转指令才不会执行。查看该地址处的值:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs shell">(gdb) x/s 0x4028d4<br>0x4028d4:"DrEvil"<br></code></pre></td></tr></table></figure><p>发现字符是“DrEvil”,所以上面的第3个参数,应该是“DrEvil”。</p><p>为了确保猜测是正确的,在<code>0x4017fc</code>地址处打一个断点,就可以查看到底<code>0x6048b0</code>地址处的前两个数字是什么了。</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs shell">(gdb) b *0x4017fc<br>Breakpoint 1 at 0x4017fc<br>(gdb) run<br>Starting program: /mnt/cgshare/bomb<br>Welcome to my fiendish little bomb. You have 6 phases with<br>which to blow yourself up. Have a nice day!<br>For NASA, space is still a high priority.<br><br>Phase 1 defused. How about the next one?<br>0 1 3 6 10 15<br><br>That's number 2. Keep going!<br>0 z 101<br><br>Halfway there!<br>13 31<br><br>So you got that one. Try this one.<br>011124<br><br>Good work! On to the next...<br>2 3 4 5 6 1<br><br>Breakpoint 1, 0x00000000004017fc in phase_defused ()<br>(gdb) x/s 0x6048b0<br>0x6048b0 <input_strings+240>:"13 31"<br></code></pre></td></tr></table></figure><p>好了,<code>0x6048b0</code>地址处的前两个数字确实是在phase4时输入的两个数。所以,只需要在phase4时多输入一个字符串“DrEvil”就可以进入secret_phase了。</p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs assembly">000000000040129b <secret_phase>:<br> 40129b:53 push %rbx<br> 40129c:e8 00 04 00 00 callq 4016a1 <read_line> <br> 4012a1:ba 0a 00 00 00 mov $0xa,%edx ; edx赋值为10 作第3个参数<br> 4012a6:be 00 00 00 00 mov $0x0,%esi ; esi赋值为0 作第2个参数<br> 4012ab:48 89 c7 mov %rax,%rdi ; read_line函数的返回值作第1个参数<br> 4012ae:e8 4d f9 ff ff callq 400c00 <strtol@plt><br> 4012b3:48 89 c3 mov %rax,%rbx <br> 4012b6:8d 40 ff lea -0x1(%rax),%eax<br> 4012b9:3d e8 03 00 00 cmp $0x3e8,%eax<br> 4012be:76 05 jbe 4012c5 <secret_phase+0x2a> ; strtol函数的返回值小于等于1001<br> 4012c0:e8 64 03 00 00 callq 401629 <explode_bomb><br> 4012c5:89 de mov %ebx,%esi ; strol函数的返回值作第2个参数<br> 4012c7:bf 10 41 60 00 mov $0x604110,%edi ; 地址处的值作第1个参数<br> 4012cc:e8 8c ff ff ff callq 40125d <fun7><br> 4012d1:83 f8 04 cmp $0x4,%eax ; func7返回值要为4<br> 4012d4:74 05 je 4012db <secret_phase+0x40><br> 4012d6:e8 4e 03 00 00 callq 401629 <explode_bomb><br> 4012db:bf b0 25 40 00 mov $0x4025b0,%edi<br> 4012e0:e8 5b f8 ff ff callq 400b40 <puts@plt><br> 4012e5:e8 dd 04 00 00 callq 4017c7 <phase_defused><br> 4012ea:5b pop %rbx<br> 4012eb:c3 retq <br> 4012ec:0f 1f 40 00 nopl 0x0(%rax)<br></code></pre></td></tr></table></figure><p>secret_phase涉及到的关键部分是两个函数调用,<code>strtol</code>和<code>fun7</code>。</p><p><code>strtol</code>函数将字符串转为整数,转换后的值要小于等于1001。所以输入的数字要小于等于1001。</p><p><code>fun7</code>函数传入2个参数,第一个参数是一个地址处的值,第2个参数是我们输入的值。</p><p>查看一下这个地址处的数据,使用十六进制,32位为一个内存单元,查看60个单元:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs shell">(gdb) x/xw 0x604110<br>0x604110 <n1>:0x00000024<br>(gdb) x/20xw 0x604110<br>0x604110 <n1>:0x000000240x000000000x006041300x00000000<br>0x604120 <n1+16>:0x006041500x000000000x000000000x00000000<br>0x604130 <n21>:0x000000080x000000000x006041b00x00000000<br>0x604140 <n21+16>:0x006041700x000000000x000000000x00000000<br>0x604150 <n22>:0x000000320x000000000x006041900x00000000<br></code></pre></td></tr></table></figure><p>可以看到这又是一个定义好的数据结构,叫做“n”,好像不是很明显,而且输出也不太规整,每个“n”应该是占用32个字节的,中间16个字节应该是两个地址,现在以8个字节为单位查看60个单元:</p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs shell">0x604110 <n1>:0x00000000000000240x0000000000604130<br>0x604120 <n1+16>:0x00000000006041500x0000000000000000<br>0x604130 <n21>:0x00000000000000080x00000000006041b0<br>0x604140 <n21+16>:0x00000000006041700x0000000000000000<br>0x604150 <n22>:0x00000000000000320x0000000000604190<br>0x604160 <n22+16>:0x00000000006041d00x0000000000000000<br>0x604170 <n32>:0x00000000000000160x0000000000604290<br>0x604180 <n32+16>:0x00000000006042500x0000000000000000<br>0x604190 <n33>:0x000000000000002d0x00000000006041f0<br>0x6041a0 <n33+16>:0x00000000006042b00x0000000000000000<br>0x6041b0 <n31>:0x00000000000000060x0000000000604210<br>0x6041c0 <n31+16>:0x00000000006042700x0000000000000000<br>0x6041d0 <n34>:0x000000000000006b0x0000000000604230<br>0x6041e0 <n34+16>:0x00000000006042d00x0000000000000000<br>0x6041f0 <n45>:0x00000000000000280x0000000000000000<br>0x604200 <n45+16>:0x00000000000000000x0000000000000000<br>0x604210 <n41>:0x00000000000000010x0000000000000000<br>0x604220 <n41+16>:0x00000000000000000x0000000000000000<br>0x604230 <n47>:0x00000000000000630x0000000000000000<br>0x604240 <n47+16>:0x00000000000000000x0000000000000000<br>0x604250 <n44>:0x00000000000000230x0000000000000000<br>0x604260 <n44+16>:0x00000000000000000x0000000000000000<br>0x604270 <n42>:0x00000000000000070x0000000000000000<br>0x604280 <n42+16>:0x00000000000000000x0000000000000000<br>0x604290 <n43>:0x00000000000000140x0000000000000000<br>0x6042a0 <n43+16>:0x00000000000000000x0000000000000000<br>0x6042b0 <n46>:0x000000000000002f0x0000000000000000<br>0x6042c0 <n46+16>:0x00000000000000000x0000000000000000<br>0x6042d0 <n48>:0x00000000000003e90x0000000000000000<br>0x6042e0 <n48+16>:0x00000000000000000x0000000000000000<br></code></pre></td></tr></table></figure><p>不难发现,每个结点的第一个8字节是数据,中间2个八字节分别是下两个结点的地址,最后1个8字节是0。所以,这个结构实际上是一个二叉树。而传入的第一个参数,自然是二叉树的首地址了。二叉树的结构如下:</p><p><img src="https://akboom20.github.io/Images/BombLab/secret_phase.png"></p><figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br></pre></td><td class="code"><pre><code class="hljs assembly">000000000040125d <fun7>:<br> 40125d:48 83 ec 08 sub $0x8,%rsp ; 开辟8个字节栈空间<br> 401261:48 85 ff test %rdi,%rdi <br> 401264:74 2b je 401291 <fun7+0x34> ; 第1个参数是0则返回-1<br> 401266:8b 17 mov (%rdi),%edx ; 取第1个参数地址处的值赋给edx<br> 401268:39 f2 cmp %esi,%edx <br> 40126a:7e 0d jle 401279 <fun7+0x1c> ;如果值小于等于第2个参数 则跳转<br> <br> 40126c:48 8b 7f 08 mov 0x8(%rdi),%rdi ; rdi+=8<br> 401270:e8 e8 ff ff ff callq 40125d <fun7> ; 递归<br> <br> 401275:01 c0 add %eax,%eax ; eax *=2<br> 401277:eb 1d jmp 401296 <fun7+0x39> ; 返回eax<br> <br> 401279:b8 00 00 00 00 mov $0x0,%eax ; eax置0<br> 40127e:39 f2 cmp %esi,%edx<br> 401280:74 14 je 401296 <fun7+0x39> ; edx = 第2个参数则返回0<br> 401282:48 8b 7f 10 mov 0x10(%rdi),%rdi ; rdi+=16<br> 401286:e8 d2 ff ff ff callq 40125d <fun7> ; 递归<br> 40128b:8d 44 00 01 lea 0x1(%rax,%rax,1),%eax ; eax = 2*rax+1<br> 40128f:eb 05 jmp 401296 <fun7+0x39> ; 返回 eax<br> <br> 401291:b8 ff ff ff ff mov $0xffffffff,%eax<br> 401296:48 83 c4 08 add $0x8,%rsp<br> 40129a:c3 retq <br></code></pre></td></tr></table></figure><p><code>fun7</code>函数是个递归函数,看起来还是很简短的,逻辑也较为简单。递归函数翻译成C代码更容易。</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">fun7</span><span class="hljs-params">(<span class="hljs-type">int</span> * t, <span class="hljs-type">int</span> input)</span><br>{<br> <span class="hljs-keyword">if</span>(t == null) <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br> <span class="hljs-type">int</span> v = * t;<br> <span class="hljs-keyword">if</span> (v <= input)<br> {<br> <span class="hljs-type">int</span> r = <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">if</span>(v == input) <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br> <span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span>*fun7(rdi+<span class="hljs-number">16</span>,input)+<span class="hljs-number">1</span>;<br> }<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">return</span> <span class="hljs-number">2</span>*fun7(rdi+<span class="hljs-number">8</span>,input);<br>}<br></code></pre></td></tr></table></figure><p>如果利用数据结构的知识更好理解:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-type">int</span> <span class="hljs-title function_">fun7</span><span class="hljs-params">(Node* current, <span class="hljs-type">int</span> input)</span><br>{<br><span class="hljs-keyword">if</span> (current == <span class="hljs-literal">NULL</span>)<br><span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br><span class="hljs-type">int</span> key = current->data;<br><span class="hljs-keyword">if</span> (input > key)<br><span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * fun7(current->rchild, input) + <span class="hljs-number">1</span>;<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (input == key)<br><span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br><span class="hljs-keyword">else</span><br><span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * fun7(current->lchild, input);<br>}<br></code></pre></td></tr></table></figure><p>这个例子对我们理解数据结构在内存中的本质也很有用。</p><p>最后只需要找到返回值为4的input就可以了,我这里根据上面的值建立了一颗二叉树,然后遍历一下就能拿到答案。</p><p>C代码:</p><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br></pre></td><td class="code"><pre><code class="hljs c"><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><iostream></span></span><br><span class="hljs-meta">#<span class="hljs-keyword">include</span> <span class="hljs-string"><stdio.h></span></span><br><br><span class="hljs-keyword">typedef</span> <span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Node</span> {</span><br><span class="hljs-type">int</span> data;<br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Node</span>* <span class="hljs-title">lchild</span>;</span><br><span class="hljs-class"><span class="hljs-keyword">struct</span> <span class="hljs-title">Node</span>* <span class="hljs-title">rchild</span>;</span><br>}Node;<br><span class="hljs-comment">//使用数组存放数据,注意是按照一层一层的排列 -1代表为空</span><br><span class="hljs-type">int</span> <span class="hljs-built_in">array</span>[] = { <span class="hljs-number">36</span>,<span class="hljs-number">8</span>,<span class="hljs-number">50</span>,<span class="hljs-number">6</span>,<span class="hljs-number">22</span>,<span class="hljs-number">45</span>,<span class="hljs-number">107</span>,<span class="hljs-number">1</span>,<span class="hljs-number">7</span>,<span class="hljs-number">20</span>,<span class="hljs-number">35</span>,<span class="hljs-number">40</span>,<span class="hljs-number">47</span>,<span class="hljs-number">99</span>,<span class="hljs-number">1001</span> };<br><span class="hljs-comment">//构建二叉树</span><br><span class="hljs-type">void</span> <span class="hljs-title function_">createTree</span><span class="hljs-params">(Node** node, <span class="hljs-type">int</span> index)</span> {<br><span class="hljs-keyword">if</span> (index <= (<span class="hljs-keyword">sizeof</span>(<span class="hljs-built_in">array</span>) / <span class="hljs-keyword">sizeof</span>(<span class="hljs-type">int</span>))) {<br><span class="hljs-keyword">if</span> (<span class="hljs-built_in">array</span>[index - <span class="hljs-number">1</span>] == <span class="hljs-number">-1</span>) {<br>*node = <span class="hljs-literal">NULL</span>;<br><span class="hljs-keyword">return</span>;<br>}<br><span class="hljs-keyword">else</span> {<br>*node = (Node*)<span class="hljs-built_in">malloc</span>(<span class="hljs-keyword">sizeof</span>(Node));<br>(*node)->data = <span class="hljs-built_in">array</span>[index - <span class="hljs-number">1</span>];<br>createTree(&((*node)->lchild), index * <span class="hljs-number">2</span>);<br>createTree(&((*node)->rchild), index * <span class="hljs-number">2</span> + <span class="hljs-number">1</span>);<br>}<br>}<br><span class="hljs-keyword">else</span> {<br>*node = <span class="hljs-literal">NULL</span>;<br><span class="hljs-keyword">return</span>;<br>}<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">fun7</span><span class="hljs-params">(Node* current, <span class="hljs-type">int</span> input)</span><br>{<br><span class="hljs-keyword">if</span> (current == <span class="hljs-literal">NULL</span>)<br><span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;<br><br><span class="hljs-type">int</span> key = current->data;<br><span class="hljs-keyword">if</span> (input > key)<br><span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * fun7(current->rchild, input) + <span class="hljs-number">1</span>;<br><span class="hljs-keyword">else</span> <span class="hljs-keyword">if</span> (input == key)<br><span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br><span class="hljs-keyword">else</span><br><span class="hljs-keyword">return</span> <span class="hljs-number">2</span> * fun7(current->lchild, input);<br>}<br><br><span class="hljs-type">int</span> <span class="hljs-title function_">main</span><span class="hljs-params">(<span class="hljs-type">int</span> argc, <span class="hljs-type">char</span>* args[])</span> {<br>Node* <span class="hljs-type">node_t</span>;<br>createTree(&<span class="hljs-type">node_t</span>, <span class="hljs-number">1</span>);<br><span class="hljs-type">int</span> i = <span class="hljs-number">0</span>;<br><span class="hljs-keyword">while</span> (i<<span class="hljs-number">50</span>) {<br>i++;<br><span class="hljs-built_in">printf</span>(<span class="hljs-string">"%d: %d\n"</span>, i,fun7(<span class="hljs-type">node_t</span>, i));<span class="hljs-comment">//也可以只打印返回值是4的i</span><br>}<br><span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;<br>}<br></code></pre></td></tr></table></figure><p>最终答案是7。</p><p>总结一下7个题的答案:</p><figure class="highlight apache"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs apache"><span class="hljs-attribute">For</span> NASA, space is still a high priority.<br><span class="hljs-attribute">0</span> <span class="hljs-number">1</span> <span class="hljs-number">3</span> <span class="hljs-number">6</span> <span class="hljs-number">10</span> <span class="hljs-number">15</span><br><span class="hljs-attribute">0</span> z <span class="hljs-number">101</span><br><span class="hljs-attribute">13</span> <span class="hljs-number">31</span> DrEvil<br><span class="hljs-attribute">011124</span><br><span class="hljs-attribute">2</span> <span class="hljs-number">3</span> <span class="hljs-number">4</span> <span class="hljs-number">5</span> <span class="hljs-number">6</span> <span class="hljs-number">1</span><br><span class="hljs-attribute">7</span><br></code></pre></td></tr></table></figure><h3 id="实验收获"><a href="#实验收获" class="headerlink" title="实验收获"></a>实验收获</h3><p>解决这7个phase,我觉得我对汇编代码的阅读理解能力,对内存和地址的认识不止上升了一个档次,而且实验过程趣味性和成就感非常的强。炸弹爆炸的背景真的让人在输入答案的时候不得不更加小心翼翼,反复检查逻辑漏洞,一遍遍的看代码也也让我熟练度大幅提升。另外如果没有网上的博客解析,完全独立的解决这些phase,挑战性是可想而知的,当然收获也肯定更大。可见国外的计算机教育理念和体系确实优异,期待下一个实验!</p><p><font color="red">原创文章 请勿随意转载,如需转载可与我联系。<font></font></font></p>]]></content>
<categories>
<category>CSAPP</category>
</categories>
<tags>
<tag>X86</tag>
<tag>汇编语言</tag>
<tag>C语言</tag>
<tag>CSAPP</tag>
</tags>
</entry>
<entry>
<title>ElasticSearch</title>
<link href="/2022/10/22/ElasticSearch/"/>
<url>/2022/10/22/ElasticSearch/</url>
<content type="html"><![CDATA[<h2 id="ElasticSearch"><a href="#ElasticSearch" class="headerlink" title="ElasticSearch"></a>ElasticSearch</h2><p>elasticsearch是一款非常强大的<strong>开源搜索引擎</strong>,可以帮助我们从海量数据中快速找到需要的内容。</p><p>elasticsearch结合kibana、Logstash、Beats,即<strong>elastic stack(ELK)</strong>。被广泛应用在<strong>日志数据分析</strong>、<strong>实时监控</strong>等领域。</p><p>eleasticsearch是ELK的核心,负责存储、搜索、分析数据。</p><p><img src="/2022/10/22/ElasticSearch/ELK.jpg"></p><p><strong>Lucene</strong>是一个Java语言的<strong>搜索引擎类库</strong>,是Apache公司的顶级项目,由DougCutting于1999年研发。</p><p>优势:</p><ul><li>易扩展</li><li>高性能(基于倒排索引)</li></ul><p>缺点:</p><ul><li>只限于java语言开发</li><li>学习曲线陡峭</li><li>不支持水平扩展</li></ul><p>相比较于lucene,eleasticsearch具备优势:</p><ul><li>支持分布式,可水平扩展</li><li>提供Restful接口,可被任何语言调用</li></ul><h3 id="正向索引和倒排索引"><a href="#正向索引和倒排索引" class="headerlink" title="正向索引和倒排索引"></a>正向索引和倒排索引</h3><p>传统数据库(如Mysql)采用正向索引。</p><p>elasticsearch采用倒排索引:</p><ul><li>文档(document):每条数据就是一个文档</li><li>词条(term):文档按照语义分成的词语</li></ul><p>正向索引先查文档,再看是否包含词条</p><p>倒排索引先查词条,在查对应的文档</p><h3 id="ES与Mysql对比"><a href="#ES与Mysql对比" class="headerlink" title="ES与Mysql对比"></a>ES与Mysql对比</h3><p>elasticsearch是面向文档存储的,可以是数据库中的一条商品数据,一个订单信息。</p><p>文档数据会被序列化为<strong>json格式</strong>后存储在elasticsearch中。</p><p><strong>索引(index)</strong>:相同类型文档的集合</p><p><img src="/2022/10/22/ElasticSearch/index.jpg"></p><p><strong>映射(mapping)</strong>:索引中文档的字段约束信息,类似表的结构约束</p><p><img src="/2022/10/22/ElasticSearch/%E6%A6%82%E5%BF%B5%E5%AF%B9%E6%AF%94.jpg"></p><ul><li><p>Mysql:擅长事务类型的操作,可以确保数据的安全性和一致性</p></li><li><p>ElasticSearch:擅长海量数据的搜索、分析、计算</p></li></ul><p><img src="/2022/10/22/ElasticSearch/mysql%E5%92%8Ces.jpg"></p><h3 id="分词器"><a href="#分词器" class="headerlink" title="分词器"></a>分词器</h3><p>es在<strong>创建倒排索引时</strong>需要对文档进行分词;</p><p><strong>在搜索时</strong>,需要对用户输入内容分词。</p><p>但默认的分词规则对中文处理不友好。</p><p>处理中文分词,一般会使用<strong>IK分词器</strong>。</p><p>IK分词器包含两种模式:</p><ul><li><p><code>ik_smart</code>:只能切分,最少切分,粗粒度</p></li><li><p><code>ik_max_word</code>:最细切分,细粒度</p></li></ul><h4 id="拓展词库和停用词库"><a href="#拓展词库和停用词库" class="headerlink" title="拓展词库和停用词库"></a>拓展词库和停用词库</h4><p>要拓展ik分词器的词库,只需要修改一个ik分词器目录中的config目录中的IkAnanlzer.cfg.xml文件。</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-meta"><?xml version=<span class="hljs-string">"1.0"</span> encoding=<span class="hljs-string">"UTF-8"</span>?></span><br><span class="hljs-meta"><!DOCTYPE <span class="hljs-keyword">properties</span> <span class="hljs-keyword">SYSTEM</span> <span class="hljs-string">"http://java.sun.com/dtd/properties.dtd"</span>></span><br><span class="hljs-tag"><<span class="hljs-name">properties</span>></span><br><span class="hljs-tag"><<span class="hljs-name">comment</span>></span>IK Analyzer 扩展配置<span class="hljs-tag"></<span class="hljs-name">comment</span>></span><br><span class="hljs-comment"><!--用户可以在这里配置自己的扩展字典 --></span><br><span class="hljs-tag"><<span class="hljs-name">entry</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"ext_dict"</span>></span>ext.dic<span class="hljs-tag"></<span class="hljs-name">entry</span>></span><br> <span class="hljs-comment"><!--用户可以在这里配置自己的扩展停止词字典--></span><br><span class="hljs-tag"><<span class="hljs-name">entry</span> <span class="hljs-attr">key</span>=<span class="hljs-string">"ext_stopwords"</span>></span>stopword.dic<span class="hljs-tag"></<span class="hljs-name">entry</span>></span><br><span class="hljs-comment"><!--用户可以在这里配置远程扩展字典 --></span><br><span class="hljs-comment"><!-- <entry key="remote_ext_dict">words_location</entry> --></span><br><span class="hljs-comment"><!--用户可以在这里配置远程扩展停止词字典--></span><br><span class="hljs-comment"><!-- <entry key="remote_ext_stopwords">words_location</entry> --></span><br><span class="hljs-tag"></<span class="hljs-name">properties</span>></span><br><br></code></pre></td></tr></table></figure><p>然后在对应文件中添加要拓展的词或者要禁止的词。</p><h3 id="索引库操作"><a href="#索引库操作" class="headerlink" title="索引库操作"></a>索引库操作</h3><h4 id="mapping属性"><a href="#mapping属性" class="headerlink" title="mapping属性"></a>mapping属性</h4><p>mapping时对索引库中文档的约束,常见的mapping属性包括:</p><ul><li><p>type:字段数据类型,常见的简单类型:</p><ul><li>字符串:text(可分词的文本)、keyword(精确值,例如:品牌、国家、ip地址等)、</li><li>数值:long integer short byte double float</li><li>布尔:boolean</li><li>日期:date</li><li>对象:object</li></ul></li><li><p>index:是否创建索引,默认为true</p></li><li><p>analyzer:使用哪种分词器,只有text的字符串需要分词</p></li><li><p>properties:该字段的子字段</p></li></ul><h4 id="创建索引库"><a href="#创建索引库" class="headerlink" title="创建索引库"></a>创建索引库</h4><p>ES中通过Restful请求来操作索引库、文档。请求内容用DSL语句来表示。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><code class="hljs json">#创建索引库<br>PUT /test<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"mappings"</span><span class="hljs-punctuation">:</span> <br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"properties"</span><span class="hljs-punctuation">:</span> <br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"info"</span><span class="hljs-punctuation">:</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"text"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"analyzer"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"ik_smart"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"email"</span><span class="hljs-punctuation">:</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"keyword"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"index"</span><span class="hljs-punctuation">:</span> <span class="hljs-literal"><span class="hljs-keyword">false</span></span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"object"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"properties"</span><span class="hljs-punctuation">:</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"firstName"</span><span class="hljs-punctuation">:</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"keyword"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lastName"</span><span class="hljs-punctuation">:</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"keyword"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><h4 id="查看、删除索引库"><a href="#查看、删除索引库" class="headerlink" title="查看、删除索引库"></a>查看、删除索引库</h4><p>查看:<code>GET /索引库名</code></p><figure class="highlight routeros"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs routeros"><span class="hljs-comment">#查询</span><br><span class="hljs-built_in">GET</span> /test<br></code></pre></td></tr></table></figure><p>删除 :<code>DELETE /索引库名</code></p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><code class="hljs bash"><span class="hljs-comment">#删除</span><br>DELETE /test<br></code></pre></td></tr></table></figure><h4 id="修改索引库"><a href="#修改索引库" class="headerlink" title="修改索引库"></a>修改索引库</h4><p>索引库和mapping一旦创建<strong>无法修改</strong>,但是<strong>可以添加</strong>新的字段:</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs json">PUT /索引库名/_mapping<br><span class="hljs-punctuation">{</span><br><span class="hljs-attr">"properties"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br><span class="hljs-attr">"新字段名"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br><span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"interger"</span><br><span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br><br>PUT /test/_mapping<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"properties"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"age"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"type"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"integer"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><h3 id="文档操作"><a href="#文档操作" class="headerlink" title="文档操作"></a>文档操作</h3><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs json">#插入文档<br>POST /test/_doc/<span class="hljs-number">1</span><br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"info"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"陈奥林程序员"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"email"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"[email protected]"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"firstName"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"奥林"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lastName"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"陈"</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br>#查询文档<br>GET /test/_doc/<span class="hljs-number">1</span><br>#删除文档<br>DELETE /test/_doc/<span class="hljs-number">1</span><br></code></pre></td></tr></table></figure><p>修改文档</p><ul><li>方式一:全量修改,会删除旧文档,添加新文档。如果id不存在会创建文档</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs json">#全量修改文档<br>PUT /test/_doc/<span class="hljs-number">1</span><br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"info"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"不知名程序员"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"email"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"[email protected]"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"firstName"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"奥林"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lastName"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"陈"</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><ul><li>方式二:增量修改,修改指定的字段</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs json">#局部修改<br>POST /test/_update/<span class="hljs-number">1</span><br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"doc"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"info"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"nb程序员"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><h3 id="RestClient操作索引库"><a href="#RestClient操作索引库" class="headerlink" title="RestClient操作索引库"></a>RestClient操作索引库</h3><h4 id="索引库操作基本步骤"><a href="#索引库操作基本步骤" class="headerlink" title="索引库操作基本步骤"></a>索引库操作基本步骤</h4><ul><li>初始化<code>RestHighLevelClient</code></li><li>创建<code>XxxxIndexRequest</code>。xxx是<code>Create,Get,Delete</code></li><li>准备<code>DSL</code>(Create时需要)</li><li>发送请求。调用对应的`RestHighLevelClient.indices().xxx()``方法</li></ul><h4 id="创建索引库-1"><a href="#创建索引库-1" class="headerlink" title="创建索引库"></a>创建索引库</h4><p>ES官方提供了各种不同语言的客户端,用来操作ES。这些客户端的本质就是<strong>组装DSL语句</strong>,通过http请求发送给ES。</p><p><img src="/2022/10/22/ElasticSearch/Restclient%E6%93%8D%E4%BD%9C%E7%B4%A2%E5%BC%95%E5%BA%93.jpg"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">void</span> <span class="hljs-title function_">createHotelIndex</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//1.创建Request对象</span><br> CreateIndexRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">CreateIndexRequest</span>(<span class="hljs-string">"hotel"</span>);<br> <span class="hljs-comment">//2.准备请求参数 DSL语句</span><br> request.source(MAPPING_TEMPLATE, XContentType.JSON);<br> <span class="hljs-comment">//3.发送请求</span><br> client.indices().create(request, RequestOptions.DEFAULT);<br> }<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> String MAPPING_TEMPLATE=<span class="hljs-string">"{\n"</span> +<br> <span class="hljs-string">" \"mappings\": {\n"</span> +<br> <span class="hljs-string">" \"properties\": {\n"</span> +<br> <span class="hljs-string">" \"id\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"keyword\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"name\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"text\",\n"</span> +<br> <span class="hljs-string">" \"analyzer\": \"ik_max_word\",\n"</span> +<br> <span class="hljs-string">" \"copy_to\": \"all\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"address\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"keyword\",\n"</span> +<br> <span class="hljs-string">" \"index\": false\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"price\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"integer\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"score\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"integer\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"brand\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"keyword\",\n"</span> +<br> <span class="hljs-string">" \"copy_to\": \"all\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"city\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"keyword\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"starName\":{\n"</span> +<br> <span class="hljs-string">" \"type\":\"keyword\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"business\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"keyword\",\n"</span> +<br> <span class="hljs-string">" \"copy_to\": \"all\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"location\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"geo_point\"\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"pic\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"keyword\",\n"</span> +<br> <span class="hljs-string">" \"index\": false\n"</span> +<br> <span class="hljs-string">" },\n"</span> +<br> <span class="hljs-string">" \"all\":{\n"</span> +<br> <span class="hljs-string">" \"type\": \"text\",\n"</span> +<br> <span class="hljs-string">" \"analyzer\": \"ik_max_word\"\n"</span> +<br> <span class="hljs-string">" }\n"</span> +<br> <span class="hljs-string">" }\n"</span> +<br> <span class="hljs-string">" \n"</span> +<br> <span class="hljs-string">" }\n"</span> +<br> <span class="hljs-string">"}"</span>;<br></code></pre></td></tr></table></figure><h4 id="删除索引库"><a href="#删除索引库" class="headerlink" title="删除索引库"></a>删除索引库</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">testDeleteHotelIndex</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//1.创建Request对象</span><br> DeleteIndexRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">DeleteIndexRequest</span>(<span class="hljs-string">"hotel"</span>);<br> <span class="hljs-comment">//2.发送请求</span><br> client.indices().delete(request,RequestOptions.DEFAULT);<br>}<br></code></pre></td></tr></table></figure><h4 id="查询索引库"><a href="#查询索引库" class="headerlink" title="查询索引库"></a>查询索引库</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testExistsHotelIndex</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//1.创建Request对象</span><br> GetIndexRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">GetIndexRequest</span>(<span class="hljs-string">"hotel"</span>);<br> <span class="hljs-comment">//2.发送请求</span><br> <span class="hljs-type">boolean</span> exists= client.indices().exists(request,RequestOptions.DEFAULT);<br> <span class="hljs-comment">//3.判断是否存在</span><br> System.out.println(exists ? <span class="hljs-string">"存在"</span> : <span class="hljs-string">"不存在"</span>);<br> }<br></code></pre></td></tr></table></figure><h3 id="RestClient操作文档"><a href="#RestClient操作文档" class="headerlink" title="RestClient操作文档"></a>RestClient操作文档</h3><h4 id="文档操作基本步骤"><a href="#文档操作基本步骤" class="headerlink" title="文档操作基本步骤"></a>文档操作基本步骤</h4><ul><li>初始化<code>RestHighLevelClient</code>。</li><li>创建<code>XxxRequest</code>。Xxx是<code>Index Get Update Delete</code></li><li>准备参数。(<code>Index</code>和<code>Update</code>时需要)</li><li>发送请求。调用<code>RestHighLevelClient.xxx()</code>方法,<code>xxx</code>是<code>index get update delete</code></li></ul><h4 id="新增文档"><a href="#新增文档" class="headerlink" title="新增文档"></a>新增文档</h4><p><img src="/2022/10/22/ElasticSearch/Restclient%E6%93%8D%E4%BD%9C%E6%96%87%E6%A1%A3.jpg"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">testAddDocument</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//根据id查询酒店</span><br> Hotel hotel= hotelService.getById(<span class="hljs-number">61083L</span>);<br> <span class="hljs-comment">//转换为文档类型</span><br> HotelDoc hotelDoc=<span class="hljs-keyword">new</span> <span class="hljs-title class_">HotelDoc</span>(hotel);<br> <span class="hljs-comment">//1.准备request对象</span><br> IndexRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">IndexRequest</span>(<span class="hljs-string">"hotel"</span>).id(hotel.getId().toString());<br> <span class="hljs-comment">//2.准备json对象</span><br> request.source(JSON.toJSONString(hotelDoc), XContentType.JSON);<span class="hljs-comment">//source是酒店的数据部分</span><br> System.out.println(JSON.toJSONString(hotelDoc));<br> <span class="hljs-comment">//3.发送请求</span><br> <span class="hljs-built_in">this</span>.client.index(request, RequestOptions.DEFAULT);<br>}<br></code></pre></td></tr></table></figure><h4 id="查询文档"><a href="#查询文档" class="headerlink" title="查询文档"></a>查询文档</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">testGetDocumentById</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//1.准备request</span><br> GetRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">GetRequest</span>(<span class="hljs-string">"hotel"</span>,<span class="hljs-string">"61083"</span>);<br> <span class="hljs-comment">//2.发送请求,得到响应</span><br> GetResponse response=client.get(request,RequestOptions.DEFAULT);<br> <span class="hljs-comment">//3,解析响应结果</span><br> String json=response.getSourceAsString();<br> <span class="hljs-comment">//4.转换为对象类型</span><br> HotelDoc hotelDoc= JSON.parseObject(json,HotelDoc.class);<br> System.out.println(hotelDoc);<br>}<br></code></pre></td></tr></table></figure><h4 id="修改文档(局部更新)"><a href="#修改文档(局部更新)" class="headerlink" title="修改文档(局部更新)"></a>修改文档(局部更新)</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">testUpdateDocument</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//1.准备request对象</span><br> UpdateRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">UpdateRequest</span>(<span class="hljs-string">"hotel"</span>,<span class="hljs-string">"61083"</span>);<br> <span class="hljs-comment">//2.准备请求参数</span><br> request.doc(<span class="hljs-string">"price"</span>,<span class="hljs-string">"999"</span>,<span class="hljs-string">"starName"</span>,<span class="hljs-string">"奥迪双钻"</span>);<br> <span class="hljs-comment">//3.发送请求</span><br> client.update(request,RequestOptions.DEFAULT);<br>}<br></code></pre></td></tr></table></figure><h4 id="删除文档"><a href="#删除文档" class="headerlink" title="删除文档"></a>删除文档</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">testDeleteDocument</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//1.准备request对象</span><br> DeleteRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">DeleteRequest</span>(<span class="hljs-string">"hotel"</span>,<span class="hljs-string">"61083"</span>);<br> <span class="hljs-comment">//2.发送请求</span><br> client.delete(request,RequestOptions.DEFAULT);<br>}<br></code></pre></td></tr></table></figure><h4 id="批量导入文档"><a href="#批量导入文档" class="headerlink" title="批量导入文档"></a>批量导入文档</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">testBulkRequest</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//批量查询酒店数据</span><br> List<Hotel> hotelList=hotelService.list();<br> <span class="hljs-comment">//1.创建bulkrequest对象</span><br> BulkRequest bulkRequest=<span class="hljs-keyword">new</span> <span class="hljs-title class_">BulkRequest</span>();<br> <span class="hljs-comment">//2.准备多个参数,添加多个新增的request</span><br> <span class="hljs-keyword">for</span> (Hotel hotel : hotelList){<br> <span class="hljs-comment">//转换为hoteldoc</span><br> HotelDoc hotelDoc=<span class="hljs-keyword">new</span> <span class="hljs-title class_">HotelDoc</span>(hotel);<br> bulkRequest.add(<span class="hljs-keyword">new</span> <span class="hljs-title class_">IndexRequest</span>(<span class="hljs-string">"hotel"</span>)<br> .id(hotelDoc.getId().toString())<br> .source(JSON.toJSONString(hotelDoc),XContentType.JSON));<br> }<br> <span class="hljs-comment">//3.发送请求</span><br> client.bulk(bulkRequest,RequestOptions.DEFAULT);<br>}<br></code></pre></td></tr></table></figure><h3 id="Domain-Specific-Language查询"><a href="#Domain-Specific-Language查询" class="headerlink" title="Domain Specific Language查询"></a>Domain Specific Language查询</h3><ul><li><p>查询所有:查询出所有的数据,一般测试用。例如:<code>match_all</code></p></li><li><p>全文检索(<code>full text</code>)查询:利用分词器对用户输入的内容进行分词,然后去倒排索引库中匹配。例如:</p><ul><li><code>match_query</code>:对用户输入内容分词,然后去倒排索引库检索</li><li><code>multi_match_query</code>:与match类似,允许同时查询多个字段</li></ul></li><li><p>精确查询:根据精确词条值查找数据,一般是查找keyword、数值、日期、boolean、等类型字段。例如:</p><ul><li><code>ids</code></li><li><code>range</code>:根据值的范围查询</li><li><code>term</code>:根据词条精确值查询</li></ul></li><li><p>地理(geo)查询:根据经纬度查询。例如:</p><ul><li><code>geo_distance</code>:查询到指定<strong>中心点</strong>小于某个距离值的所有文档</li><li><code>geo_bounding_box</code>:查询geo_point值落在某个<strong>矩形</strong>范围内的所有文档</li></ul></li><li><p>复合查询(compound)查询:合并查询可以将上述各种查询条件组合起来,合并查询条件,实现更复杂的逻辑查询。例如:</p><ul><li><code>boolean</code>:布尔查询是一个或多个查询子句的组合</li><li><code>function_score</code>:算分函数查询,可以控制文档相关性算分,控制文档排名,例如百度竞价。</li></ul></li></ul><h4 id="查询DSL基本语法"><a href="#查询DSL基本语法" class="headerlink" title="查询DSL基本语法"></a>查询DSL基本语法</h4><ul><li><code>GET /索引库名/_search</code></li><li><code>{"query":{"查询类型":{"FIELD":"TEXT"}}}</code></li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br></pre></td><td class="code"><pre><code class="hljs json">#查询所有<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"match_all"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span> <br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br>#全文检索查询——match<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"match"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"外滩"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br>#全文检索查询——multi_match<br>#和copy to效果一样,但是查询效率低<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"multi_match"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"外滩如家"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"fields"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><span class="hljs-string">"name"</span><span class="hljs-punctuation">,</span><span class="hljs-string">"brand"</span><span class="hljs-punctuation">,</span><span class="hljs-string">"business"</span><span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br>#精确查询——term<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"term"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"city"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"value"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"杭州"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br>#精确查询——range<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"range"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"price"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"gte"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1000</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lte"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">2000</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br>#地理查询——geo_bounding_box<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"geo_bounding_box"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"location"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"top_left"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"lat"</span><span class="hljs-punctuation">:</span><span class="hljs-number">31.1</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lon"</span><span class="hljs-punctuation">:</span><span class="hljs-number">121.5</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"bottom_right"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"lat"</span><span class="hljs-punctuation">:</span><span class="hljs-number">30.9</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lon"</span><span class="hljs-punctuation">:</span><span class="hljs-number">121.7</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br>#地理查询——geo_distance<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <br> <span class="hljs-attr">"geo_distance"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"distance"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"15km"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"location"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"31.251433, 121.47522"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><h4 id="相关性算分"><a href="#相关性算分" class="headerlink" title="相关性算分"></a>相关性算分</h4><p>当我们利用match查询时,文档结果会根据与搜索词条的关联度打分(__score),返回结果时按照分值降序排列。</p><p><img src="/2022/10/22/ElasticSearch/%E7%9B%B8%E5%85%B3%E6%80%A7%E7%AE%97%E6%B3%95.jpg"></p><ul><li><strong>TF-IDF</strong>:在elasticsearch5.0之前,会随着词频增加而越来越大</li><li><strong>BM25</strong>:在elasticsearch5.0之后,会随着词频的增加而增大,但增长的曲线会区域水平</li></ul><h4 id="Function-Score-Query"><a href="#Function-Score-Query" class="headerlink" title="Function Score Query"></a>Function Score Query</h4><p><img src="/2022/10/22/ElasticSearch/functionscorequery.jpg"></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs json"># function score query<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"function_score"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"match"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"all"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"外滩"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"functions"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"filter"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"term"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"brand"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"如家"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"weight"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">10</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"boost_mode"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"sum"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p>三要素:</p><ul><li>过滤条件:那些文档要加分</li><li>算分函数:如何计算function score</li><li>加权方式:function score与query score如何计算</li></ul><h4 id="Boolean-Query"><a href="#Boolean-Query" class="headerlink" title="Boolean Query"></a>Boolean Query</h4><p>一个或多个查询子句的组合,子查询的组合方式有:</p><ul><li><code>must</code>:必须匹配每个子查询,类似“与”</li><li><code>should</code>:选择性匹配子查询,类似“或”</li><li><code>must_not</code>:必须不匹配,不参与算分,类似“非”</li><li><code>filter</code>:必须匹配,不参与算分</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs json">GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"bool"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"must"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"match"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"如家"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"must_not"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><span class="hljs-attr">"range"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"price"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"gt"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">400</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span> <br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"filter"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"geo_distance"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"distance"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"10km"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"location"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"lat"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">31.21</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lon"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">121.5</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span> <br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><h3 id="搜索结果处理"><a href="#搜索结果处理" class="headerlink" title="搜索结果处理"></a>搜索结果处理</h3><h5 id="排序"><a href="#排序" class="headerlink" title="排序"></a>排序</h5><p>es支持对搜索结果排序,默认是按照相关度算分(_score)来排序,可以排序的字段类型有:<strong>keyword类型,数值类型,地理坐标类型,日期类型</strong>。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-comment">//sort排序</span><br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"range"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"price"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"gte"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">1000</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lte"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">2000</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"sort"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"price"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"order"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"desc"</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"score"</span><span class="hljs-punctuation">:</span><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"order"</span><span class="hljs-punctuation">:</span><span class="hljs-string">"asc"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">}</span><br><span class="hljs-comment">//地理排序</span><br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"match_all"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"sort"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"_geo_distance"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"location"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"lat"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">31.034661</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"lon"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">-121.612282</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"order"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"asc"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"unit"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"km"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><br><span class="hljs-punctuation">}</span><br><br></code></pre></td></tr></table></figure><h5 id="分页"><a href="#分页" class="headerlink" title="分页"></a>分页</h5><p>es默认情况下只返回top10的数据,如果要查询更多的数据就需要修改分页参数。</p><p>es中通过修改<code>from</code>、<code>size</code>参数来控制要返回的分页结果。</p><ul><li>form:分页开始的位置,默认为0</li><li>size:期望获取的文档总数</li></ul><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs json">#分页查询<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"match_all"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"sort"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"price"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"order"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"desc"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"from"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">0</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"size"</span><span class="hljs-punctuation">:</span> <span class="hljs-number">20</span><br><span class="hljs-punctuation">}</span><br></code></pre></td></tr></table></figure><p><strong>深度分页问题</strong></p><p>es是分布式的,所以会面临深度分页问题。对于es集群,必须聚合所有结果,重新排序截取前size个。</p><p>如果搜索页数过深,或者结果集from+size越大,对内存和CPU消耗也就越高,因此es设定结果集的查询上限是10000。</p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs json"><span class="hljs-attr">"error"</span> <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"root_cause"</span> <span class="hljs-punctuation">:</span> <span class="hljs-punctuation">[</span><br> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"type"</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">"illegal_argument_exception"</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"reason"</span> <span class="hljs-punctuation">:</span> <span class="hljs-string">"Result window is too large, from + size must be less than or equal to: [10000] but was [10019]. See the scroll api for a more efficient way to request large data sets. This limit can be set by changing the [index.max_result_window] index level setting."</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">]</span><span class="hljs-punctuation">,</span><br></code></pre></td></tr></table></figure><p><strong>解决方案</strong></p><ul><li>_search after_:分页时需要排序,原理是从上一次的排序值开始,查询下一页数据。官方推荐</li><li>_scroll_:原理将排序数据形成快照,保存到内存。官方不推荐</li></ul><p><img src="/2022/10/22/ElasticSearch/%E5%88%86%E9%A1%B5%E6%96%B9%E5%BC%8F%E5%AF%B9%E6%AF%94.jpg"></p><h5 id="高亮"><a href="#高亮" class="headerlink" title="高亮"></a>高亮</h5><p>在搜索结果中把搜索关键字突出显示。</p><p>原理:</p><ul><li>将搜索结果中的关键字用标签标记出来</li><li>在页面中给出标签添加css样式</li></ul><p><em>默认情况下,ES搜索字段必须与高亮字段一致</em></p><figure class="highlight json"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs json">#高亮显示<br>GET /hotel/_search<br><span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"query"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"match"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"all"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"如家"</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><span class="hljs-punctuation">,</span><br> <span class="hljs-attr">"highlight"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"fields"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"name"</span><span class="hljs-punctuation">:</span> <span class="hljs-punctuation">{</span><br> <span class="hljs-attr">"require_field_match"</span><span class="hljs-punctuation">:</span> <span class="hljs-string">"false"</span><br> <br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br> <span class="hljs-punctuation">}</span><br><span class="hljs-punctuation">}</span><br><br></code></pre></td></tr></table></figure><h3 id="RestClient查询文档"><a href="#RestClient查询文档" class="headerlink" title="RestClient查询文档"></a>RestClient查询文档</h3><ul><li>创建<code>SearchRequest</code>对象</li><li>准备<code>Request.source()</code>,也就是DSL<ul><li><code>QueryBuilders</code>来构建查询条件</li><li>传入<code>Request.source()</code>的<code>query()</code>方法</li></ul></li><li>发送请求,得到结果</li><li>解析结果(参考JSON结果,从外到内,逐层解析)</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testMatchAll</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//1.准备request</span><br> SearchRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchRequest</span>(<span class="hljs-string">"hotel"</span>);<br> <span class="hljs-comment">//2.准备DSL</span><br> request.source().query(QueryBuilders.matchAllQuery());<br> <span class="hljs-comment">//3.发送请求</span><br> SearchResponse response= client.search(request, RequestOptions.DEFAULT);<br> <br> <br> <br> <span class="hljs-comment">//4.解析结果</span><br> SearchHits searchHits=response.getHits();<br> <span class="hljs-comment">//4.1获取总条数</span><br> <span class="hljs-type">long</span> total=searchHits.getTotalHits().value;<br> <span class="hljs-comment">//4.2获取文档数组</span><br> SearchHit[] hits=searchHits.getHits();<br> <span class="hljs-comment">//4.3遍历</span><br> <span class="hljs-keyword">for</span> (SearchHit hit:hits){<br> <span class="hljs-comment">//获取文档source</span><br> String json=hit.getSourceAsString();<br> <span class="hljs-comment">//反序列化</span><br> HotelDoc doc= JSON.parseObject(json,HotelDoc.class);<br> System.out.println(doc);<br> }<br> }<br> <span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testBoolean</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//1.准备request</span><br> SearchRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchRequest</span>(<span class="hljs-string">"hotel"</span>);<br> <span class="hljs-comment">//2.准备DSL</span><br> <span class="hljs-comment">//2.1准备BooleanQuery</span><br> <span class="hljs-type">BoolQueryBuilder</span> <span class="hljs-variable">boolQuery</span> <span class="hljs-operator">=</span> QueryBuilders.boolQuery();<br> <span class="hljs-comment">//2.2添加term</span><br> boolQuery.must(QueryBuilders.termQuery(<span class="hljs-string">"city"</span>,<span class="hljs-string">"上海"</span>));<br> <span class="hljs-comment">//2.3添加range</span><br> boolQuery.filter(QueryBuilders.rangeQuery(<span class="hljs-string">"price"</span>).lte(<span class="hljs-number">250</span>));<br><br> request.source().query(boolQuery);<br> <span class="hljs-comment">//3.发送请求</span><br> SearchResponse response= client.search(request, RequestOptions.DEFAULT);<br><br> handleResponse(response);<br> }<br><br></code></pre></td></tr></table></figure><p>RestAPI中其中构建DSL是通过<code>HighLevelRestClient</code>中的<code>resource()</code>来实现的,其中包括<strong>查询,排序,分页,高亮</strong>等所有功能。</p><p>构建查询条件的核心部分是由一个名为<code>QueryBUilders</code>的工具类提供的,其中包括了<strong>各种查询方法</strong>。</p><p><strong>要构建查询条件,只要记住一个类:QueryBuilders</strong></p><h3 id="RestClient排序和分页"><a href="#RestClient排序和分页" class="headerlink" title="RestClient排序和分页"></a>RestClient排序和分页</h3><p>搜索结果和的排序和分页是与query同级的参数。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br><span class="hljs-keyword">void</span> <span class="hljs-title function_">testPageAndSort</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//页码,每页大小</span><br> <span class="hljs-type">int</span> page=<span class="hljs-number">2</span>,size=<span class="hljs-number">5</span>;<br> <span class="hljs-comment">//1.准备request</span><br> SearchRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchRequest</span>(<span class="hljs-string">"hotel"</span>);<br> <span class="hljs-comment">//2.准备DSL</span><br> <span class="hljs-comment">//2.1准备Query</span><br> request.source().query(QueryBuilders.matchAllQuery());<br> <span class="hljs-comment">//2.2排序sort</span><br> request.source().sort(<span class="hljs-string">"price"</span>, SortOrder.ASC);<br> <span class="hljs-comment">//2.3分页from,size</span><br> request.source().from((page-<span class="hljs-number">1</span>)*size).size(size);<br> <span class="hljs-comment">//3.发送请求</span><br> SearchResponse response= client.search(request, RequestOptions.DEFAULT);<br><br> handleResponse(response);<br>}<br></code></pre></td></tr></table></figure><h3 id="RestClient高亮"><a href="#RestClient高亮" class="headerlink" title="RestClient高亮"></a>RestClient高亮</h3><p>高亮API包括请求DSL构建和结果解析两部分。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><code class="hljs java"> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">handleResponse</span><span class="hljs-params">(SearchResponse response)</span> {<br> <span class="hljs-comment">//4.解析结果</span><br> SearchHits searchHits= response.getHits();<br> <span class="hljs-comment">//4.1获取总条数</span><br> <span class="hljs-type">long</span> total=searchHits.getTotalHits().value;<br> <span class="hljs-comment">//4.2获取文档数组</span><br> SearchHit[] hits=searchHits.getHits();<br> <span class="hljs-comment">//4.3遍历</span><br> <span class="hljs-keyword">for</span> (SearchHit hit:hits){<br> <span class="hljs-comment">//获取文档source</span><br> String json=hit.getSourceAsString();<br> <span class="hljs-comment">//反序列化</span><br> HotelDoc doc= JSON.parseObject(json,HotelDoc.class);<br><br> <span class="hljs-comment">//获取高亮结果</span><br> Map<String, HighlightField> highlightFields=hit.getHighlightFields();<br><br> <span class="hljs-keyword">if</span> (!CollectionUtils.isEmpty(highlightFields)) {<br> <span class="hljs-comment">//根据字段名获取高亮结果</span><br> <span class="hljs-type">HighlightField</span> <span class="hljs-variable">highlightField</span> <span class="hljs-operator">=</span> highlightFields.get(<span class="hljs-string">"name"</span>);<br> <span class="hljs-comment">//获取高亮值</span><br> <span class="hljs-type">String</span> <span class="hljs-variable">name</span> <span class="hljs-operator">=</span> highlightField.getFragments()[<span class="hljs-number">0</span>].toString();<br> <span class="hljs-comment">//覆盖非高亮结果</span><br> doc.setName(name);<br> }<br> System.out.println(doc);<br> }<br> }<br><span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testHighLight</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> IOException {<br> <span class="hljs-comment">//页码,每页大小</span><br> <span class="hljs-type">int</span> page=<span class="hljs-number">2</span>,size=<span class="hljs-number">5</span>;<br> <span class="hljs-comment">//1.准备request</span><br> SearchRequest request=<span class="hljs-keyword">new</span> <span class="hljs-title class_">SearchRequest</span>(<span class="hljs-string">"hotel"</span>);<br> <span class="hljs-comment">//2.准备DSL</span><br> <span class="hljs-comment">//2.1准备Query</span><br> request.source().query(QueryBuilders.matchQuery(<span class="hljs-string">"all"</span>,<span class="hljs-string">"如家"</span>));<br> <span class="hljs-comment">//2.2高亮</span><br> request.source().highlighter(<span class="hljs-keyword">new</span> <span class="hljs-title class_">HighlightBuilder</span>().field(<span class="hljs-string">"name"</span>).requireFieldMatch(<span class="hljs-literal">false</span>));<br> <span class="hljs-comment">//3.发送请求</span><br> SearchResponse response= client.search(request, RequestOptions.DEFAULT);<br><br> handleResponse(response);<br> }<br></code></pre></td></tr></table></figure><p><strong>所有DSL的构建,记住source()方法!</strong></p>]]></content>
<tags>
<tag>SpringCloud</tag>
<tag>ElasticSearch</tag>
</tags>
</entry>
<entry>
<title>SpringAMQP</title>
<link href="/2022/10/22/SpringAMQP/"/>
<url>/2022/10/22/SpringAMQP/</url>
<content type="html"><![CDATA[<h3 id="MQ——MessageQueue"><a href="#MQ——MessageQueue" class="headerlink" title="MQ——MessageQueue"></a>MQ——MessageQueue</h3><p>消息队列,字面意思来看就是存放消息的队列。也就是事件驱动架构中的Broker。</p><p>知名MQ服务:</p><p><img src="/2022/10/22/SpringAMQP/MQ%E5%AF%B9%E6%AF%94.jpg"></p><h2 id="RabbitMQ"><a href="#RabbitMQ" class="headerlink" title="RabbitMQ"></a>RabbitMQ</h2><p>RabbitMQ是基于<strong>Erlang</strong>语言开发的开源消息通信中间件。</p><h3 id="结构和概念"><a href="#结构和概念" class="headerlink" title="结构和概念"></a>结构和概念</h3><p><img src="/2022/10/22/SpringAMQP/rabblitmq%E7%BB%93%E6%9E%84.jpg"></p><ul><li><strong>channel</strong>:操作MQ的的工具</li><li><strong>exchange</strong>:路由消息到队列中</li><li><strong>queue</strong>:缓存消息</li><li><strong>vitural host</strong>:虚拟主机,是对queue、exchange等资源的逻辑分组</li></ul><h3 id="消息模型"><a href="#消息模型" class="headerlink" title="消息模型"></a>消息模型</h3><p>MQ的官方文档中给出了5个MQ的Demo实例了,对应几种不同的用法:</p><ul><li><p>基本消息队列(BasicQueue)</p></li><li><p>工作消息队列(WorkQueue)</p></li><li><p>发布订阅(Publish、Subscribe),又根据交换机类型不同分为三种:</p><ul><li>Fanout Exchange:广播</li><li>Direct Exchange:路由</li><li>Topic Exchange:主题</li></ul></li></ul><h4 id="HelloWorld案例"><a href="#HelloWorld案例" class="headerlink" title="HelloWorld案例"></a>HelloWorld案例</h4><p>官方的HelloWorld是基于最杰出的消息队列模型来实现的,只包括三个角色:</p><ul><li><strong>Publisher</strong>:消息发布者,将消息发送到队列queue</li><li><strong>queue</strong>:消息队列,负责接收并缓存消息</li><li><strong>consumer</strong>:订阅队列,处理队列中的消息</li></ul><p>基本消息队列的消息发送流程:</p><ol><li>建立connection</li><li>创建channel</li><li>利用channel声明队列</li><li>利用channel向队列发送消息</li></ol><p>基本消息队列的消息接收流程:</p><ol><li>建立connection</li><li>创建channel</li><li>利用channel声明队列</li><li>定义consume的消费行为<code>handleDelivery()</code></li><li>利用channel将消费者与队列绑定</li></ol><h2 id="SpringAMQP"><a href="#SpringAMQP" class="headerlink" title="SpringAMQP"></a>SpringAMQP</h2><p>Advanced Message Queuing Protocol:是用于在应用程序或之间传递业务消息的开放标准。该协议与语言和平台无关,更符合微服务中独立性的要求。</p><p>Spring AMQP 是基于AMQP协议定义的一套<strong>API规范</strong>,提供了模板来发送和接收消息。包含两部分,其中spring-amqp是基础抽象,spring-rabbit是底层的默认实现。</p><h3 id="HelloWorld案例-1"><a href="#HelloWorld案例-1" class="headerlink" title="HelloWorld案例"></a>HelloWorld案例</h3><ol><li><p>在父工程中引入spring-amqp的依赖</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-comment"><!--AMQP依赖,包含RabbitMQ--></span><br> <span class="hljs-tag"><<span class="hljs-name">dependency</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>org.springframework.boot<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>spring-boot-starter-amqp<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"></<span class="hljs-name">dependency</span>></span><br></code></pre></td></tr></table></figure></li><li><p>在publish服务中利用RabbitTemplate发送消息到simplequeue这个队列</p><ol><li><p>在publisher服务中编写application.yml,添加mq连接信息</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br> <span class="hljs-attr">rabbitmq:</span><br> <span class="hljs-attr">host:</span> <span class="hljs-number">49.235</span><span class="hljs-number">.83</span><span class="hljs-number">.188</span><br> <span class="hljs-attr">port:</span> <span class="hljs-number">5672</span><br> <span class="hljs-attr">username:</span> <span class="hljs-string">cal</span><br> <span class="hljs-attr">password:</span> <span class="hljs-number">123</span><br> <span class="hljs-attr">virtual-host:</span> <span class="hljs-string">/</span><br></code></pre></td></tr></table></figure></li><li><p>在publisher服务中新建测试类,编写测试方法</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RunWith(SpringRunner.class)</span><br><span class="hljs-meta">@SpringBootTest</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SpringAmqpTest</span> {<br> <span class="hljs-meta">@Autowired</span><br> <span class="hljs-keyword">private</span> RabbitTemplate rabbitTemplate;<br> <span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testSendMessage2SimpleQueue</span><span class="hljs-params">()</span>{<br> String queueName=<span class="hljs-string">"simple.queue"</span>;<br> String message=<span class="hljs-string">"hello,mother fuck!"</span>;<br> rabbitTemplate.convertAndSend(queueName,message);<br> }<br>}<br></code></pre></td></tr></table></figure></li></ol></li><li><p>在consumer服务中编写消费逻辑,绑定simplequeue这个队列</p><ol><li><p>在consumer服务中编写application.yml,添加mq连接配置</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">spring:</span><br> <span class="hljs-attr">rabbitmq:</span><br> <span class="hljs-attr">host:</span> <span class="hljs-number">49.235</span><span class="hljs-number">.83</span><span class="hljs-number">.188</span><br> <span class="hljs-attr">port:</span> <span class="hljs-number">5672</span><br> <span class="hljs-attr">username:</span> <span class="hljs-string">cal</span><br> <span class="hljs-attr">password:</span> <span class="hljs-number">123</span><br> <span class="hljs-attr">virtual-host:</span> <span class="hljs-string">/</span><br></code></pre></td></tr></table></figure></li><li><p>在consumer服务中新建一个类,编写消费逻辑</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SpringRabbitListener</span> {<br> <span class="hljs-meta">@RabbitListener(queues = "simple.queue")</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenSimpleQueue</span><span class="hljs-params">(String msg)</span>{<br> System.out.println(<span class="hljs-string">"我收到的消息"</span>+msg);<br> }<br>}<br></code></pre></td></tr></table></figure></li></ol></li></ol><h3 id="Work-Queue案例"><a href="#Work-Queue案例" class="headerlink" title="Work Queue案例"></a>Work Queue案例</h3><p>提高消息处理速度,避免堆积。</p><ol><li><p>在publisher服务中定义测试方法,每秒产生50条消息,发送到simple.queue</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testSendMessage2WorkQueue</span><span class="hljs-params">()</span>{<br> String queueName=<span class="hljs-string">"simple.queue"</span>;<br> String message=<span class="hljs-string">"hello,mother fuck!___"</span>;<br> <span class="hljs-keyword">for</span> (<span class="hljs-type">int</span> i=<span class="hljs-number">1</span>;i<=<span class="hljs-number">50</span>;i++) {<br> rabbitTemplate.convertAndSend(queueName, message + i);<br> }<br> }<br></code></pre></td></tr></table></figure></li><li><p>在consumer服务中定义两个消息监听者,都监听simple.queue队列</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RabbitListener(queues = "simple.queue")</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenSimpleQueue1</span><span class="hljs-params">(String msg)</span> <span class="hljs-keyword">throws</span> InterruptedException {<br> System.out.println(<span class="hljs-string">"消费者1收到的消息"</span>+msg+ <span class="hljs-string">" "</span>+LocalTime.now());<br> Thread.sleep(<span class="hljs-number">20</span>);<br>}<br> <br><span class="hljs-meta">@RabbitListener(queues = "simple.queue")</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenSimpleQueue2</span><span class="hljs-params">(String msg)</span> <span class="hljs-keyword">throws</span> InterruptedException {<br> System.err.println(<span class="hljs-string">"消费者2收到的消息"</span>+msg+<span class="hljs-string">" "</span>+LocalTime.now());<br> Thread.sleep(<span class="hljs-number">200</span>);<br>}<br></code></pre></td></tr></table></figure></li><li><p>消费者1每秒处理50条消息,消费者2每秒处理10条消息</p></li></ol><p><strong>消息预取限制</strong></p><p>修改application.yml,设置preFetch的值,可以控制消息预取的上限</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><code class="hljs java">spring:<br> rabbitmq:<br> host: <span class="hljs-number">49.235</span><span class="hljs-number">.83</span><span class="hljs-number">.188</span><br> port: <span class="hljs-number">5672</span><br> username: cal<br> password: <span class="hljs-number">123</span><br> virtual-host: /<br> listener:<br> simple:<br> prefetch: <span class="hljs-number">1</span><br></code></pre></td></tr></table></figure><h3 id="发布(Publish)、订阅(Subscribe)案例"><a href="#发布(Publish)、订阅(Subscribe)案例" class="headerlink" title="发布(Publish)、订阅(Subscribe)案例"></a>发布(Publish)、订阅(Subscribe)案例</h3><p>发布订阅模式与之前案例的区别就是允许将同一消息发送给多个消费者。实现方式是加入了<strong>交换机(exchange)</strong>。</p><p>常见的exchange类型:</p><ul><li><strong>Fanout</strong>:广播</li><li><strong>Direct</strong>:路由</li><li><strong>Topic</strong>:话题</li></ul><p><strong>exchange负责消息的转发,不负责存储,转发失败则消息丢失!!!</strong></p><h4 id="FanoutExchange"><a href="#FanoutExchange" class="headerlink" title="FanoutExchange"></a>FanoutExchange</h4><p>Fanout Exchange会将接收到的消息路由到每一个跟其绑定的queue。</p><p>SpringAMQP提供了声明交换机<strong>Exchange</strong>、队列<strong>Queue</strong>、绑定关系<strong>Binding</strong>的API。</p><ol><li><p>在consumer服务中,利用代码声明队列、交换机,并将两者绑定</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Configuration</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">FanoutConfig</span> {<br> <span class="hljs-comment">//1.声明交换机</span><br> <span class="hljs-meta">@Bean</span><br> <span class="hljs-keyword">public</span> FanoutExchange <span class="hljs-title function_">fanoutExchange</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">FanoutExchange</span>(<span class="hljs-string">"cal.fanout"</span>);<br> }<br> <span class="hljs-comment">//2.声明队列</span><br> <span class="hljs-meta">@Bean</span><br> <span class="hljs-keyword">public</span> Queue <span class="hljs-title function_">fanoutQueue1</span><span class="hljs-params">()</span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Queue</span>(<span class="hljs-string">"fanout.queue1"</span>);<br> }<br> <span class="hljs-comment">//3.绑定关系</span><br> <span class="hljs-meta">@Bean</span><br> <span class="hljs-keyword">public</span> Binding <span class="hljs-title function_">fanoutBinding1</span><span class="hljs-params">(Queue fanoutQueue1,FanoutExchange fanoutExchange)</span>{<br> <span class="hljs-keyword">return</span> BindingBuilder<br> .bind(fanoutQueue1).<br> to(fanoutExchange);<br> }<br> <span class="hljs-meta">@Bean</span><br> <span class="hljs-keyword">public</span> Queue <span class="hljs-title function_">fanoutQueue2</span><span class="hljs-params">()</span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Queue</span>(<span class="hljs-string">"fanout.queue2"</span>);<br> }<br> <span class="hljs-meta">@Bean</span><br> <span class="hljs-keyword">public</span> Binding <span class="hljs-title function_">fanoutBinding2</span><span class="hljs-params">(Queue fanoutQueue2,FanoutExchange fanoutExchange)</span>{<br> <span class="hljs-keyword">return</span> BindingBuilder<br> .bind(fanoutQueue2).<br> to(fanoutExchange);<br> }<br>}<br></code></pre></td></tr></table></figure></li><li><p>在counsumer服务中,编写两个消费者方法,分别监听fanout.queue1和fanout.queue2</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><code class="hljs java"> <br><span class="hljs-meta">@RabbitListener(queues = "fanout.queue1")</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenFanoutQueue1</span><span class="hljs-params">(String msg)</span> <span class="hljs-keyword">throws</span> InterruptedException {<br> System.out.println(<span class="hljs-string">"消费者收到fanout.queue1的消息:"</span>+msg);<br>}<br><span class="hljs-meta">@RabbitListener(queues = "fanout.queue2")</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenFanoutQueue2</span><span class="hljs-params">(String msg)</span> <span class="hljs-keyword">throws</span> InterruptedException {<br> System.out.println(<span class="hljs-string">"消费者收到fanout.queue2的消息:"</span>+msg);<br>}<br></code></pre></td></tr></table></figure></li><li><p>在publisher中编写测试方法,向cal.fanout发送消息</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testSendFanoutExchange</span><span class="hljs-params">()</span>{<br> <span class="hljs-type">String</span> <span class="hljs-variable">exchangeName</span> <span class="hljs-operator">=</span><span class="hljs-string">"cal.fanout"</span>;<br> String msg=<span class="hljs-string">"hello,everyone,mother fuck!"</span>;<br> rabbitTemplate.convertAndSend(exchangeName,<span class="hljs-string">""</span>,msg);<br> }<br></code></pre></td></tr></table></figure></li></ol><h4 id="DirectExchange"><a href="#DirectExchange" class="headerlink" title="DirectExchange"></a>DirectExchange</h4><p>DirectExchange会将接收到的消息根据规则路由到指定的Queue,因此称为路由模式(<strong>routes</strong>)。</p><ul><li>每一个Queue都与Exchange设置一个或多个<strong>BindingKey</strong></li><li>发布者发送消息时,指定消息的<strong>RoutingKey</strong></li><li>Exchange将消息路由到<strong>BindingKey与消息RoutingKey</strong>一致的队列</li></ul><p>当绑定多个BindingKey时,与Fanout交换机功能一样。</p><ol><li><p>利用<code>@RabbitListener</code>声明Exchange、Queue、RoutingKey</p></li><li><p>在consumer服务中,编写两个消费者服务,分别监听direct.queue1和direct.queue2</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RabbitListener(bindings = @QueueBinding(</span><br><span class="hljs-meta"> value = @Queue(name = "direct.queue1"),</span><br><span class="hljs-meta"> exchange = @Exchange(name = "cal.direct",type = ExchangeTypes.DIRECT),</span><br><span class="hljs-meta"> key = {"red","blue"}</span><br><span class="hljs-meta"> ))</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenDirectQueue1</span><span class="hljs-params">(String msg)</span>{<br> System.out.println(<span class="hljs-string">"消费者接收到的direct.queue1的消息"</span>+msg);<br> }<br> <br> <span class="hljs-meta">@RabbitListener(bindings = @QueueBinding(</span><br><span class="hljs-meta"> value = @Queue(name = "direct.queue2"),</span><br><span class="hljs-meta"> exchange = @Exchange(name = "cal.direct",type = ExchangeTypes.DIRECT),</span><br><span class="hljs-meta"> key = {"red","yellow"}</span><br><span class="hljs-meta"> ))</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenDirectQueue2</span><span class="hljs-params">(String msg)</span>{<br> System.out.println(<span class="hljs-string">"消费者接收到的direct.queue2的消息"</span>+msg);<br> }<br></code></pre></td></tr></table></figure></li><li><p>在publisher中编写测试方法,向itcast.direct发送消息</p></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testSendDirectExchange</span><span class="hljs-params">()</span>{<br> <span class="hljs-type">String</span> <span class="hljs-variable">exchangeName</span> <span class="hljs-operator">=</span><span class="hljs-string">"cal.direct"</span>;<br> String msg=<span class="hljs-string">"hello,red,mother fuck!"</span>;<br> rabbitTemplate.convertAndSend(exchangeName,<span class="hljs-string">"red"</span>,msg);<br> }<br></code></pre></td></tr></table></figure><h4 id="TopicExchange"><a href="#TopicExchange" class="headerlink" title="TopicExchange"></a>TopicExchange</h4><p>TopicExchange与DirectExchange类似,区别在于routingkey必须是多个单词的列表,并且以<code>.</code>分割。</p><p>Queue与Exchange指定BindingKey时可以使用通配符:</p><ul><li>#:代指0个或多个单词</li><li>*:代指一个单词</li></ul><ol><li><p>利用<code>@RabbitListener</code>声明Exchange、Queue、RoutingKey</p></li><li><p>在consumer服务中,编写两个消费者方法,分别监听topic.queue1和topic.queue2</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RabbitListener(bindings = @QueueBinding(</span><br><span class="hljs-meta"> value = @Queue(name = "topic.queue1"),</span><br><span class="hljs-meta"> exchange = @Exchange(name = "cal.topic",type = ExchangeTypes.TOPIC),</span><br><span class="hljs-meta"> key = "china.#"</span><br><span class="hljs-meta">))</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenTopicQueue1</span><span class="hljs-params">(String msg)</span>{<br> System.out.println(<span class="hljs-string">"消费者接收到topic.queue1的消息"</span>+msg);<br>}<br><span class="hljs-meta">@RabbitListener(bindings = @QueueBinding(</span><br><span class="hljs-meta"> value = @Queue(name = "topic.queue2"),</span><br><span class="hljs-meta"> exchange = @Exchange(name = "cal.topic",type = ExchangeTypes.TOPIC),</span><br><span class="hljs-meta"> key = "#.news"</span><br><span class="hljs-meta">))</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenTopicQueue2</span><span class="hljs-params">(String msg)</span>{<br> System.out.println(<span class="hljs-string">"消费者接收到topic.queue2的消息"</span>+msg);<br>}<br></code></pre></td></tr></table></figure></li><li><p>在publisher中编写测试方法,向cal.topic发送消息</p></li></ol><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Test</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">testSendTopicExchange</span><span class="hljs-params">()</span>{<br> <span class="hljs-type">String</span> <span class="hljs-variable">exchangeName</span> <span class="hljs-operator">=</span><span class="hljs-string">"cal.topic"</span>;<br> String msg=<span class="hljs-string">"hello,mother fuck!"</span>;<br> rabbitTemplate.convertAndSend(exchangeName,<span class="hljs-string">"china.news"</span>,msg);<br> }<br></code></pre></td></tr></table></figure><h3 id="消息转换器"><a href="#消息转换器" class="headerlink" title="消息转换器"></a>消息转换器</h3><p>Spring的对消息对象的处理时由<code>org.springframework.amqp.support.convert.MessageaConvert</code>来处理的。而默认实现是SimpleMessageConvert,基于JDK的ObjectOutputStream完成序列化。</p><p>如果要修改只需要定义一个MessgeConvert类型的Bean即可。推荐使用JSON方式序列化:</p><ol><li><p>在publisher服务引入依赖</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">dependency</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>com.fasterxml.jackson.dataformat<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">artifactId</span>></span>jackson-dataformat-xml<span class="hljs-tag"></<span class="hljs-name">artifactId</span>></span><br> <span class="hljs-tag"><<span class="hljs-name">version</span>></span>2.9.10<span class="hljs-tag"></<span class="hljs-name">version</span>></span><br><span class="hljs-tag"></<span class="hljs-name">dependency</span>></span><br></code></pre></td></tr></table></figure></li><li><p>在publisher服务的启动类声明MessageConvert:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Bean</span><br><span class="hljs-keyword">public</span> MessageConverter <span class="hljs-title function_">messageConverter</span><span class="hljs-params">()</span>{<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">Jackson2JsonMessageConverter</span>();<br>}<br></code></pre></td></tr></table></figure></li><li><p>consumer服务的操作一样</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@RabbitListener(queues = "object.queue")</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">listenObjectQueue</span><span class="hljs-params">(Map<String,Object> msg)</span>{<br> System.out.println(<span class="hljs-string">"接收到的消息"</span>+msg);<br>}<br></code></pre></td></tr></table></figure></li></ol>]]></content>
<categories>
<category>SpringCloud</category>
</categories>
<tags>
<tag>SpringCloud</tag>
<tag>RabbitMQ</tag>
</tags>
</entry>
<entry>
<title>docker</title>
<link href="/2022/10/22/docker/"/>
<url>/2022/10/22/docker/</url>
<content type="html"><![CDATA[<h3 id="Docker架构"><a href="#Docker架构" class="headerlink" title="Docker架构"></a>Docker架构</h3><h4 id="镜像和容器"><a href="#镜像和容器" class="headerlink" title="镜像和容器"></a>镜像和容器</h4><p><strong>镜像(image)</strong>:Docker将应用程序及其所需的依赖、函数库、环境、配置等文件打包在一起,称为镜像</p><p><strong>容器(container)</strong>:镜像中的应用程序运行后形成的进程就是容器,只是Docker会给容器做隔离,<strong>对外不可见</strong></p><h4 id="DockerHub"><a href="#DockerHub" class="headerlink" title="DockerHub"></a>DockerHub</h4><p>Dockerhub是一个docker镜像的托管平台,这样的平台称为Docker Registry。国内类似DockerHubde公开服务平台,如网易云镜像服务,阿里云镜像服务。</p><h4 id="Docker架构-1"><a href="#Docker架构-1" class="headerlink" title="Docker架构"></a>Docker架构</h4><p>Docker是一个CS架构的程序,由两部分组成:</p><ul><li>服务端-server:Docker守护进程,负责处理docker指令,管理镜像,容器等】</li><li>客户端-client:通过命令或RestAPI向Docker服务端发送指令。可以在本地或远程向服务端发送指令</li></ul><p><img src="/2022/10/22/docker/Docker%E6%9E%B6%E6%9E%84.jpg"></p><h3 id="Docker基本操作"><a href="#Docker基本操作" class="headerlink" title="Docker基本操作"></a>Docker基本操作</h3><p>镜像名称一般分为两部分组成:<code>[respository]:[tag]</code>,在没有指定tag时,默认是latest,代表最新版本的镜像</p><h5 id="镜像操作命令"><a href="#镜像操作命令" class="headerlink" title="镜像操作命令"></a>镜像操作命令</h5><p><img src="/2022/10/22/docker/docker%E9%95%9C%E5%83%8F%E6%93%8D%E4%BD%9C%E5%91%BD%E4%BB%A4.jpg"></p><h5 id="容器相关命令"><a href="#容器相关命令" class="headerlink" title="容器相关命令"></a>容器相关命令</h5><p><img src="/2022/10/22/docker/docker%E5%AE%B9%E5%99%A8%E7%9B%B8%E5%85%B3%E5%91%BD%E4%BB%A4.jpg"></p><figure class="highlight shell"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs shell">docker exec //进入容器执行命令<br>docker logs //查看容器运行日志<br>docker ps //查看所有运行的容器及其状态<br></code></pre></td></tr></table></figure><h3 id="数据卷"><a href="#数据卷" class="headerlink" title="数据卷"></a>数据卷</h3><p>容器与数据耦合的问题</p><ul><li>不便于修改</li><li>数据不可复用</li><li>升级维护困难</li></ul><p><strong>数据卷(volume)</strong>是一个虚拟目录,指向宿主机文件系统的某个目录。</p><h3 id="挂载数据卷"><a href="#挂载数据卷" class="headerlink" title="挂载数据卷"></a>挂载数据卷</h3><p><img src="/2022/10/22/docker/docker%E6%8C%82%E8%BD%BD.jpg"></p><ol><li>docker run 的命令通过<code>-v</code>参数挂载文件或目录到容器中:<ol><li><code>-v volume名称:容器内目录</code></li><li><code>-v 宿主机文件:容器内文件</code></li><li><code>-v 宿主机目录:容器内目录</code></li></ol></li><li>数据卷挂载与目录直接挂载<ul><li>数据卷挂载耦合度低,由docker来管理目录,但是目录较深,不好找</li><li>目录挂载耦合度高,需要自己管理目录,不过容易查找位置</li></ul></li></ol><h3 id="Docker自定义镜像"><a href="#Docker自定义镜像" class="headerlink" title="Docker自定义镜像"></a>Docker自定义镜像</h3><p><strong>镜像</strong>:镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而成。</p><p><img src="/2022/10/22/docker/mysql%E9%95%9C%E5%83%8F%E7%BB%93%E6%9E%84.jpg"></p><p>镜像是分层结构,每一层称为一个layer</p><ul><li>Baseimage层:包含基本的系统函数库、环境变量、文件系统</li><li>Entrypoint:入口,是镜像中应用的启动命令</li><li>其他:再Baseimage基础上添加依赖、安装程序、完成整个应用的安装配置</li></ul><h4 id="Dockerfile"><a href="#Dockerfile" class="headerlink" title="Dockerfile"></a>Dockerfile</h4><p><strong>Dockerfile</strong>就是一个文本文件,其中包含一个个的<strong>指令</strong>,用指令来说明要执行什么擦欧总来构建镜像。每一个指令都会形成一层layer</p><p><img src="/2022/10/22/docker/dockerfile%E6%8C%87%E4%BB%A4.jpg"></p><h3 id="DockerCompose"><a href="#DockerCompose" class="headerlink" title="DockerCompose"></a>DockerCompose</h3><p>Docker Compose可以基于Compose文件帮助我们快速部署分布式的应用,而无需手动一个个创建和运行容器。</p><p>Compose文件是一个文本文件,通过指定定义集群中的每个容器如何运行。</p><h3 id="Docker镜像仓库"><a href="#Docker镜像仓库" class="headerlink" title="Docker镜像仓库"></a>Docker镜像仓库</h3><p>公共仓库:例如Docker官方的Docker hub,国内的网易云镜像服务,阿里巴巴镜像服务</p><p>私有仓库:企业或用户自己搭建的Docker Registry</p><ol><li>推送本地镜像到仓库前都必须重命名(<code>docker tag</code>)镜像,以镜像仓库地址为前缀</li><li>镜像仓库推送前需要把仓库地址配置到docker服务的daemon.json,被docker信任</li><li>推送使用<code>docker push</code></li><li>拉取使用<code>docker pull</code></li></ol>]]></content>
<categories>
<category>Docker</category>
</categories>
<tags>
<tag>docker</tag>
</tags>
</entry>
<entry>
<title>Gateway</title>
<link href="/2022/10/22/Gateway/"/>
<url>/2022/10/22/Gateway/</url>
<content type="html"><![CDATA[<h2 id="GateWay"><a href="#GateWay" class="headerlink" title="GateWay"></a>GateWay</h2><p>网关功能:</p><ul><li>身份认证和权限认证</li><li>服务路由、负载均衡</li><li>请求限流</li></ul><p>SpringCloud网关的技术实现:</p><ul><li>gateway</li><li>zuul</li></ul><p>Zuul是基于Servlet的实现。属于<strong>阻塞式编程</strong>。而SpringCloudGateWay则是基于Spring5中提供的WebFlux,属于<strong>响应式编程</strong>的实现,具备更好的性能。</p><h4 id="搭建网关服务"><a href="#搭建网关服务" class="headerlink" title="搭建网关服务"></a>搭建网关服务</h4><ol><li>创建新的module,引入SpringCloudGateWay的依赖和nacos的服务发现依赖</li><li>编写路由配置及nacos地址</li></ol><p>路由配置包括:</p><ul><li>路由id:路由的唯一标识</li><li>路由uri:路由的目标地址,http代表固定地址,lb代表根据服务名负载均衡</li><li>路由断言predicates:判断路由的规则</li><li>路由过滤器filters:对请求或响应做处理</li></ul><h4 id="路由断言工厂Route-Predicate-Factory"><a href="#路由断言工厂Route-Predicate-Factory" class="headerlink" title="路由断言工厂Route Predicate Factory"></a>路由断言工厂Route Predicate Factory</h4><p>我们在配置文件中写的断言规则只是字符串,这些字符串会被<strong>Predicate Factory</strong>读取并处理,转变为路由判断的条件。</p><p>Spring提供了11种基本的Predicate工厂:</p><p><img src="/2022/10/22/Gateway/PredicateFactory.jpg"></p><h4 id="路由过滤器-GatewayFilter"><a href="#路由过滤器-GatewayFilter" class="headerlink" title="路由过滤器 GatewayFilter"></a>路由过滤器 GatewayFilter</h4><p>GatewayFilter时网关中提供的一种过滤器,可以对<strong>进入</strong>网关的请求和微服务<strong>返回</strong>的响应做处理。</p><p><img src="/2022/10/22/Gateway/%E8%BF%87%E6%BB%A4%E5%99%A8.jpg"></p><p>Spring中提供了31种不同的路由过滤器工厂。</p><p><img src="/2022/10/22/Gateway/%E8%BF%87%E6%BB%A4%E5%99%A8%E5%B7%A5%E5%8E%82.jpg"></p><h5 id="默认过滤器:"><a href="#默认过滤器:" class="headerlink" title="默认过滤器:"></a>默认过滤器:</h5><p>如果要对所有的路由都生效,则可以将过滤器工厂写到<code>default-filters</code>下。</p><h5 id="全局过滤器:"><a href="#全局过滤器:" class="headerlink" title="全局过滤器:"></a>全局过滤器:</h5><p>全局过滤器的作用也是处理一切进入网关的请求和微服务响应,与GatewayFilter的作用一样。</p><p>区别在于GatewatFilter通过配置定义,处理逻辑是固定的,而GlobalFilter的逻辑需要自己写代码实现。</p><p>定义方式是实现GlobalFilter接口。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//@Order(-1)</span><br><span class="hljs-meta">@Component</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AuthorizeFilter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">GlobalFilter</span> , Ordered {<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> Mono<Void> <span class="hljs-title function_">filter</span><span class="hljs-params">(ServerWebExchange exchange, GatewayFilterChain chain)</span> {<br> <span class="hljs-comment">//1.获取请求参数</span><br> ServerHttpRequest request=exchange.getRequest();<br> MultiValueMap<String,String> params=request.getQueryParams();<br><br> <span class="hljs-comment">//2.获取参数种的authorization参数</span><br> String auth=params.getFirst(<span class="hljs-string">"authorization"</span>);<br><br> <span class="hljs-comment">//3.判断参数值是否等于admin</span><br> <span class="hljs-keyword">if</span> (<span class="hljs-string">"admin"</span>.equals(auth)){<br> <span class="hljs-comment">//4.是,放行</span><br> <span class="hljs-keyword">return</span> chain.filter(exchange);<br> }<br> <span class="hljs-comment">//5.否,拦截</span><br> <span class="hljs-comment">//5.1设置状态码</span><br> exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);<br> <span class="hljs-comment">//5.2拦截请求</span><br> <span class="hljs-keyword">return</span> exchange.getResponse().setComplete();<br><br> }<br></code></pre></td></tr></table></figure><h4 id="过滤器执行顺序"><a href="#过滤器执行顺序" class="headerlink" title="过滤器执行顺序"></a>过滤器执行顺序</h4><p>请求进入网关会碰到三类过滤器:当前路由的过滤器,DefaultFilter,GlobalFilter</p><p>请求路由后,会将三种路由过滤器合并到一个过滤器链(集合)中,排序后依次执行每个过滤器。</p><p><img src="/2022/10/22/Gateway/%E8%BF%87%E6%BB%A4%E5%99%A8%E9%93%BE.jpg"></p><ul><li>每一个过滤器都必须指定一个int类型的order值,<strong>order值越小,优先级越高,执行顺序越靠前</strong></li><li>GlobalFilter通过实现Ordered接口,或者添加<code>@Order</code>注解,来指定order值,有我们自己指定</li><li>路由过滤器和defaultFilter的order由Spring指定,默认是按照声明顺序从1递增</li><li>当过滤器的order值一样时,会按照<strong>defaultFilter>路由过滤器>GlobalFilte</strong>r的顺序执行。</li></ul><p><img src="/2022/10/22/Gateway/%E8%BF%87%E6%BB%A4%E5%99%A8%E9%A1%BA%E5%BA%8F.jpg"></p><h4 id="跨域问题处理"><a href="#跨域问题处理" class="headerlink" title="跨域问题处理"></a>跨域问题处理</h4><p>跨域:域名不一致就是跨域,主要包括:</p><ul><li>域名不同</li><li>域名相同,端口不通</li></ul><p>跨域问题:<strong>浏览器禁止</strong>请求的发起者与服务端发生ajax请求,请求被浏览器拦截的问题</p><p>解决方案:CORS(跨域资源共享)</p><p>网关处理跨域采用的同样是CORS方案,并且只需要简单的配置即可。</p><p><img src="/2022/10/22/Gateway/CORS%E8%A7%A3%E5%86%B3%E8%B7%A8%E5%9F%9F.jpg"></p>]]></content>
<categories>
<category>SpringCloud</category>
</categories>
<tags>
<tag>SpringCloud</tag>
<tag>Gateway</tag>
</tags>
</entry>
<entry>
<title>Feign</title>
<link href="/2022/10/22/Feign/"/>
<url>/2022/10/22/Feign/</url>
<content type="html"><![CDATA[<h2 id="Feign"><a href="#Feign" class="headerlink" title="Feign"></a>Feign</h2><h3 id="RestTemplate方式调用存在的问题"><a href="#RestTemplate方式调用存在的问题" class="headerlink" title="RestTemplate方式调用存在的问题"></a>RestTemplate方式调用存在的问题</h3><ul><li>代码可读性差,变成体验不统一</li><li>参数复杂URL难以维护</li><li>不够优雅</li></ul><p>Feign是一个声明式的http客户端。其作用就是帮助我们优雅的实现http请求的发送,解决上面的问题。</p><h3 id="定义和使用Feign客户端"><a href="#定义和使用Feign客户端" class="headerlink" title="定义和使用Feign客户端"></a>定义和使用Feign客户端</h3><ol><li>引入依赖</li><li>在order-service的启动类添加注解<code>@EnableFeignClients</code>开启Feign的功能</li><li>编写Feign客户端使用<code>@FeignClient</code>注解</li></ol><p>主要是基于SpringMVC的注解来生命远程调用的信息,比如:</p><ul><li>服务名称</li><li>请求方式</li><li>请求参数</li><li>返回值类型</li></ul><p><strong>Feign已经集成了Ribbion,自动实现负载均衡</strong></p><h3 id="Feign自定义配置"><a href="#Feign自定义配置" class="headerlink" title="Feign自定义配置"></a>Feign自定义配置</h3><p>Feign运行自定义配置来覆盖默认配置,可以修改的配置如下:</p><p><img src="/2022/10/22/Feign/Feign%E8%87%AA%E5%AE%9A%E4%B9%89%E9%85%8D%E7%BD%AE.jpg"></p><p>一般需要更改的是<strong>日志级别</strong>。</p><p>配置Feign日志有两种方式:</p><p>方式一:配置文件方式</p><ol><li>全局生效</li></ol><p><img src="/2022/10/22/Feign/Feign%E6%97%A5%E5%BF%97%E9%85%8D%E7%BD%AE%E5%85%A8%E5%B1%80%E7%94%9F%E6%95%88.jpg"></p><ol><li>某个服务生效</li></ol><p><img src="/2022/10/22/Feign/Feign%E6%97%A5%E5%BF%97%E9%85%8D%E7%BD%AE%E5%B1%80%E9%83%A8%E7%94%9F%E6%95%88.jpg"></p><p>方式二:Java代码配置,需要声明一个Bean</p><p><img src="/2022/10/22/Feign/Feign%E6%97%A5%E5%BF%97%E9%85%8D%E7%BD%AEJava%E4%BB%A3%E7%A0%81%E6%96%B9%E5%BC%8F.jpg"></p><ol><li><p>如果是全局配置,将类放到<code>@EnableFeignClients</code>注解中:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@EnableFeignClients(defaultConfiguration=FeignClientConfiguration.class)</span><br></code></pre></td></tr></table></figure></li><li><p>如果是局部配置,则把它放到<code>@FeignClient</code>这个注解中:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FeignClient(value="userservice",configuratoin=FeignClientConfiguration.class")</span><br></code></pre></td></tr></table></figure></li></ol><h3 id="Feign性能优化"><a href="#Feign性能优化" class="headerlink" title="Feign性能优化"></a>Feign性能优化</h3><p>Feign底层的客户端实现</p><ul><li>URLConnection:默认实现,不支持连接池</li><li>Apach HttpClient:支持连接池</li><li>OKHttp:支持连接池</li></ul><p>优化Feign的性能主要包括:</p><ol><li>使用连接池代替默认的URLConnection</li><li>日志级别,最好使用basic或none</li></ol><h4 id="连接池配置"><a href="#连接池配置" class="headerlink" title="连接池配置"></a>连接池配置</h4><ol><li><p>引入依赖:</p><figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs xml"><span class="hljs-tag"><<span class="hljs-name">dependency</span>></span><br><span class="hljs-tag"><<span class="hljs-name">groupId</span>></span>io.github.openfeign<span class="hljs-tag"></<span class="hljs-name">groupId</span>></span><br><span class="hljs-tag"><<span class="hljs-name">aratifactId</span>></span>feign-httpclient<span class="hljs-tag"><<span class="hljs-name">aratifactId</span>></span><br><span class="hljs-tag"></<span class="hljs-name">dependency</span>></span><br></code></pre></td></tr></table></figure></li><li><p>配置连接池</p></li></ol><h3 id="Feign最佳实践"><a href="#Feign最佳实践" class="headerlink" title="Feign最佳实践"></a>Feign最佳实践</h3><h4 id="继承"><a href="#继承" class="headerlink" title="继承"></a>继承</h4><p>给<strong>消费者的FeignClient</strong>和<strong>提供者的controller</strong>定义统一的父接口作为标准</p><p> 缺点:</p><ul><li>服务紧耦合</li><li>父接口参数列表中的映射不会被继承</li></ul><h4 id="抽取"><a href="#抽取" class="headerlink" title="抽取"></a>抽取</h4><p>将FeignClient抽取为<strong>独立模块</strong>,并且把接口有关的<strong>POJO</strong>、<strong>默认的Feign配置</strong>都放到这个模块中 ,提供给所有消费者使用。</p><p>步骤:</p><ol><li>首先新建一个module,然后引入feign的stater依赖</li><li>将order-service中编写的UserClient、User、DefaultFeignCOnfiguration都赋值到新模块中</li><li>在order-service中引入新模块</li></ol><p>当定义的FeignClient不在SpringBootApplication的扫描包范围时,这些FeignClient无法使用。有两种方式解决:</p><p>方式一:指定FeignClient所在包</p><p>方式二:指定FeignClient字节码</p>]]></content>
<categories>
<category>SpringCloud</category>
</categories>
<tags>
<tag>SpringCloud</tag>
<tag>Feign</tag>
</tags>
</entry>
<entry>
<title>Nacos</title>
<link href="/2022/10/22/Nacos/"/>
<url>/2022/10/22/Nacos/</url>
<content type="html"><![CDATA[<h2 id="Nacos"><a href="#Nacos" class="headerlink" title="Nacos"></a>Nacos</h2><h3 id="服务注册到Nacos"><a href="#服务注册到Nacos" class="headerlink" title="服务注册到Nacos"></a>服务注册到Nacos</h3><ol><li>在cloud-demo父工程中添加<code>spring-cloud-alibaba</code>的管理依赖</li><li>注释掉orderservice和userservice中原有的erureka依赖</li><li>添加nacos客户端依赖</li></ol><h3 id="Nacos服务分级存储模型"><a href="#Nacos服务分级存储模型" class="headerlink" title="Nacos服务分级存储模型"></a>Nacos服务分级存储模型</h3><p>服务 集群 实例</p><p><img src="/2022/10/22/Nacos/Nacos%E9%9B%86%E7%BE%A4.jpg"></p><p>服务调用尽可能选择本地集群的服务,跨集群调用延迟较高,本地集群不可访问是,再去访问其他集群。</p><h3 id="NacosRule负载均衡策略"><a href="#NacosRule负载均衡策略" class="headerlink" title="NacosRule负载均衡策略"></a>NacosRule负载均衡策略</h3><p>优先选择本地集群,在本地集群中随机选择,本地没有服务才会跨集群。</p><p>Nacos提供了权重负载均衡:在Nacos控制台可以设置实例的权重值,选中实例后面的编辑按钮。权重为0不会被访问。可以做到平滑升级服务。</p><h3 id="环境隔离"><a href="#环境隔离" class="headerlink" title="环境隔离"></a>环境隔离</h3><p><strong>Namespace</strong>:Nacos中<strong>服务存储</strong>和<strong>数据存储</strong>的最外层都是一个名为namespace的东西,用来做最外层隔离。不同命名空间的实例不可见。</p><h3 id="临时实例和非临时实例"><a href="#临时实例和非临时实例" class="headerlink" title="临时实例和非临时实例"></a>临时实例和非临时实例</h3><p>服务注册到Nacos时,可以选择注册为临时或者非临时实例。</p><h3 id="Nacos配置管理"><a href="#Nacos配置管理" class="headerlink" title="Nacos配置管理"></a>Nacos配置管理</h3><h4 id="统一配置管理"><a href="#统一配置管理" class="headerlink" title="统一配置管理"></a>统一配置管理</h4><ul><li>配置更改热更新</li></ul><p>配置获取步骤如下:</p><p><img src="/2022/10/22/Nacos/Nacos%E9%85%8D%E7%BD%AE%E8%8E%B7%E5%8F%96%E6%AD%A5%E9%AA%A4.jpg"></p><h4 id="将服务交给Nacos管理的步骤"><a href="#将服务交给Nacos管理的步骤" class="headerlink" title="将服务交给Nacos管理的步骤"></a>将服务交给Nacos管理的步骤</h4><ol><li>在Nacos中添加配置文件</li><li>在微服务中引入nacos的config依赖</li><li>在微服务中添加bootstrap.yml,配置nacos地址,当前环境、服务名称、文件后缀名。这些决定了程序启动时去读取nacos的哪个配置文件</li></ol><h4 id="配置热更新"><a href="#配置热更新" class="headerlink" title="配置热更新"></a>配置热更新</h4><p>Nacos配置文件变更后,微服务无需重写启动就可以感知。需要配置:</p><p>方式一:在<code>@value</code>注入的变量所在<strong>类</strong>上添加注解<code>@RefreshScope</code></p><p>方式二:使用<code>@ConfigurationProperties</code>注解(<strong>推荐</strong>)</p><h4 id="多环境配置共享"><a href="#多环境配置共享" class="headerlink" title="多环境配置共享"></a>多环境配置共享</h4><ul><li>微服务启动时会从nacos读取多个配置文件<ol><li>服务名-环境.yaml</li><li>服务名.yaml</li></ol></li></ul><p>无论profile如何变化,服务名.yaml这个文件一定会加载,因此多环境共享配置可以写入这个文件。</p><p>多种配置文件优先级:</p><p>服务名-profile.yaml>服务名.yaml>本地配置</p><h4 id="Nacos集群搭建"><a href="#Nacos集群搭建" class="headerlink" title="Nacos集群搭建"></a>Nacos集群搭建</h4><p>基本步骤:</p><ul><li>搭建数据库,初始化数据库表结构</li><li>下载Nacos安装包</li><li>配置Nacos</li><li>启动nacos集群</li><li>nginx反向代理</li></ul>]]></content>
<categories>
<category>SpringCloud</category>
</categories>
<tags>
<tag>SpringCloud</tag>
<tag>Nacos</tag>
</tags>
</entry>
<entry>
<title>Eureka</title>
<link href="/2022/10/22/Eureka/"/>
<url>/2022/10/22/Eureka/</url>
<content type="html"><![CDATA[<h2 id="Eureka"><a href="#Eureka" class="headerlink" title="Eureka"></a>Eureka</h2><h3 id="服务调用出现的问题"><a href="#服务调用出现的问题" class="headerlink" title="服务调用出现的问题"></a>服务调用出现的问题</h3><ol><li><p>服务消费者该如何获取其服务提供者的地址信息?</p></li><li><p>如果有多个服务提供者,消费者该如何选择?</p></li><li><p>消费者如何得知服务提供者的健康状态?</p></li></ol><h3 id="Eureka的作用"><a href="#Eureka的作用" class="headerlink" title="Eureka的作用"></a>Eureka的作用</h3><ul><li>eureka-server:服务端,注册中心<ul><li>记录服务信息</li><li>心跳监控</li></ul></li><li>eureka-client:客户端<ul><li>Provider:服务提供者<ul><li>注册自己的信息到EurekaServer</li><li>每隔30秒发送一次心跳</li></ul></li><li>Consumer:服务消费者<ul><li>根据服务名称从EurekaServer中拉取服务列表</li><li>基于服务列表做负载均衡,选中一个微服务后发起远程调用</li></ul></li></ul></li></ul><h3 id="问题解决"><a href="#问题解决" class="headerlink" title="问题解决"></a>问题解决</h3><ol><li><p>服务提供者启动时向eureka注册自己的信息,eureka保存这些信息,消费者根据服务名称向eureka拉取提供者信息</p></li><li><p>服务消费者利用负载均衡算法,从服务列表中挑选一个</p></li><li><p>服务提供者会每隔30秒向EurekaServer发送心跳请求,报告健康状况,eureka会更新记录服务列表信息,心跳不正常会被剔除,消费者就可以拉取代最新的信息</p></li></ol><h3 id="搭建EurekaServer"><a href="#搭建EurekaServer" class="headerlink" title="搭建EurekaServer"></a>搭建EurekaServer</h3><p>步骤:</p><ol><li>创建项目,引入<code>spring-cloud-stater-netflix-eureka-server</code>的依赖</li><li>编写启动类,添加<code>@EnableEurekaServer</code>注解</li><li>添加<code>application.yml</code>文件,编写配置</li></ol><h4 id="服务注册"><a href="#服务注册" class="headerlink" title="服务注册"></a>服务注册</h4><h5 id="将user-service服务注册到EurekaServer"><a href="#将user-service服务注册到EurekaServer" class="headerlink" title="将user-service服务注册到EurekaServer"></a>将user-service服务注册到EurekaServer</h5><ol><li><p>在user-service项目引入<code>spring-cloud-starter-netflix-eureka-client</code>依赖</p></li><li><p>在application.yml文件,编写配置:</p><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs dts"><span class="hljs-symbol">spring:</span><br><span class="hljs-symbol">application:</span><br><span class="hljs-symbol">name:</span> userservice<br><span class="hljs-symbol"> eureka:</span><br><span class="hljs-symbol"> client:</span><br> service-url:<br><span class="hljs-symbol"> defaultZone:</span>http:<span class="hljs-comment">//127.0.0.1:10086/eureka/</span><br></code></pre></td></tr></table></figure></li></ol><h4 id="服务发现"><a href="#服务发现" class="headerlink" title="服务发现"></a>服务发现</h4><ol><li>修改OrderService的代码,修改访问的url路径,用服务名代替IP,端口</li><li>在orderservice项目的启动类orderapplication中的RestTemplate添加<strong>负载均衡</strong>注解<code>@LoadBlanced</code></li></ol><h3 id="Ribbon负载均衡"><a href="#Ribbon负载均衡" class="headerlink" title="Ribbon负载均衡"></a>Ribbon负载均衡</h3><h4 id="负载均衡流程"><a href="#负载均衡流程" class="headerlink" title="负载均衡流程"></a>负载均衡流程</h4><p> <img src="/2022/10/22/Eureka/Ribbon%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E6%B5%81%E7%A8%8B.jpg"></p><h4 id="负载均衡策略"><a href="#负载均衡策略" class="headerlink" title="负载均衡策略"></a>负载均衡策略</h4><p>Ribbon的负载均衡策略是一个叫做IRule的接口来定义的,每个子接口都是一种规则:</p><p><img src="/2022/10/22/Eureka/IRule.jpg"></p><p><img src="/2022/10/22/Eureka/%E8%B4%9F%E8%BD%BD%E5%9D%87%E8%A1%A1%E7%AD%96%E7%95%A5%E8%AF%A6%E8%A7%A3.jpg"></p><p>通过定义IRule实现可以修改负载均衡策略,有两种方式:</p><ol><li><p><strong>代码方式</strong>:在orderservice中的orderapplication类中,定义一个新的IRule:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Bean</span><br><span class="hljs-keyword">public</span> IRule <span class="hljs-title function_">randomRule</span><span class="hljs-params">()</span>{<br><span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">RandomRule</span>();<br>}<br></code></pre></td></tr></table></figure></li><li><p><strong>配置文件方式</strong>:在order-service的application.yml中,添加新的配置也可修改规则:</p><figure class="highlight dts"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs dts"><span class="hljs-symbol">userservice:</span><br><span class="hljs-symbol">ribbon:</span><br><span class="hljs-symbol">NFLoadBanlancerRuleClassName:</span>com.netflix.loadbanlancer.RandomRule<span class="hljs-meta">#负载均衡规则</span><br></code></pre></td></tr></table></figure></li></ol><h4 id="饥饿加载和懒加载"><a href="#饥饿加载和懒加载" class="headerlink" title="饥饿加载和懒加载"></a>饥饿加载和懒加载</h4><p>Ribbon默认都是采用<strong>懒加载</strong>,即第一次访问时才会去创建LoadBalanceClient,请求时间会很长。而<strong>饥饿加载</strong>则会在项目启动时创建,降低第一次访问的耗时,可以通过配置文件开启饥饿加载:</p><figure class="highlight yaml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs yaml"><span class="hljs-attr">ribbon:</span><br><span class="hljs-attr">eager-load:</span><br><span class="hljs-attr">enabled:</span> <span class="hljs-literal">true</span> <span class="hljs-comment">#开启饥饿加载</span><br><span class="hljs-attr">client:</span> <br><span class="hljs-string">-userservice</span> <span class="hljs-comment">#指定对userservice这个服务开启饥饿加载</span><br></code></pre></td></tr></table></figure>]]></content>
<categories>
<category>SpringCloud</category>
</categories>
<tags>
<tag>SpringCloud</tag>
<tag>Eureka</tag>
</tags>
</entry>
<entry>
<title>Annotation</title>
<link href="/2022/10/22/Annotation/"/>
<url>/2022/10/22/Annotation/</url>
<content type="html"><![CDATA[<h2 id="什么是Java-Annotation"><a href="#什么是Java-Annotation" class="headerlink" title="什么是Java Annotation"></a>什么是Java Annotation</h2><p>Annotation是从JDK5.0开始引入的新技术。</p><h2 id="Annotation的作用"><a href="#Annotation的作用" class="headerlink" title="Annotation的作用"></a>Annotation的作用</h2><ul><li>不是程序本身,可以对程序作出解释。</li><li>可以被其他程序(比如编译器)读取</li></ul><h2 id="Annotationd的格式"><a href="#Annotationd的格式" class="headerlink" title="Annotationd的格式"></a>Annotationd的格式</h2><p>注解是以”@注解名”在代码中存在的,还可以添加一些参数值。例如@SuppressWarning(value=”unchecked”)</p><h2 id="Annotation在哪里使用"><a href="#Annotation在哪里使用" class="headerlink" title="Annotation在哪里使用"></a>Annotation在哪里使用</h2><p>可以在package、class、method、filed等等上面,相当于给他们添加了额外的辅助信息,我们可以通过反射机制编程实现对这些元数据的访问。</p><h2 id="Java内置注解"><a href="#Java内置注解" class="headerlink" title="Java内置注解"></a>Java内置注解</h2><p>__@Override__:定义在Java.lang.Override中,此注解只适用于修辞方法,表示一个方法声明打算重写超类中的另一个方法。</p><p>__@Deprecated__:定义在Java.lang.Deprecated中,此注解可以用于修辞方法,属性,类,不鼓励程序员使用这样的元素,通常是因为它很危险或者存在更好的选择。</p><p>__@SuppressWarnings__:定义在Java.lang.SuppressWarnings中,用来抑制编译时的警告信息。需要添加一个参数才能使用。</p><h2 id="元注解"><a href="#元注解" class="headerlink" title="元注解"></a>元注解</h2><p>元注解的作用就是负责注解其他的注解。Java定义了4个标准的meta-annotation类型,他们被用来提供对其他注解类型的说明。这些类型和它们所支持的类在Java.lang.annotation包中可以找到</p><ul><li>__@Target__:描述注解使用的范围。</li><li>__@Retention__:表示需要在什么级别保存该注释信息,用于描述注解的声明周期。(SOURCE<CLASS<RUNTIME)</li><li><strong>@Documented</strong>:说明该注解将包含在javadoc中</li><li><strong>@Inherited</strong>:说明子类可以继承父类中的该注解</li></ul><h2 id="自定义注解"><a href="#自定义注解" class="headerlink" title="自定义注解"></a>自定义注解</h2><p>使用__@Interface__自定义注解时,自动继承了__java.lang.annotation.Annotation__接口</p><p>分析:</p><ul><li>__@Interface__用来声明一个注解,格式:public @Interface 注解名 {定义内容}</li><li>其中的每一个方法实际上时声明了一个配置参数。</li><li>方法的名称就是参数的名称、</li><li>返回值类型就是参数的类型(返回值只能是基本类型,Class,String,enum)</li><li>可以通过default来声明参数的默认值</li><li>如果只有一个参数成员,一般参数名为value</li><li>注解元素必须要有值,我们定义注解元素时,经常使用空字符串,0作为默认值。</li></ul>]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>Reflection</title>
<link href="/2022/10/22/Reflection/"/>
<url>/2022/10/22/Reflection/</url>
<content type="html"><![CDATA[<h2 id="Java反射机制概述"><a href="#Java反射机制概述" class="headerlink" title="Java反射机制概述"></a>Java反射机制概述</h2><h3 id="动态语言"><a href="#动态语言" class="headerlink" title="动态语言"></a>动态语言</h3><p>是一类在运行时可以改变其结构的语言:例如新的函数、对象、甚至代码可以被引进,已有的函数可以被删除或者其他结构上的变化。通俗的说就是在运行时可以根据某些条件改变其自身结构。</p><p>例如:Object-C、C#、JavaScript、PHP、Python</p><h3 id="静态语言"><a href="#静态语言" class="headerlink" title="静态语言"></a>静态语言</h3><p>与动态语言相比,运行时结构不可变的语言就是静态语言。如Java,C,C++</p><p>Java不是动态用语言,但Java可以称之为“准动态语言”。即Java有一定的动态性,我们可以利用Java反射机制获得类似动态语言的特性。Java的动态性让编程更加灵活。</p><h2 id="Java-Reflection"><a href="#Java-Reflection" class="headerlink" title="Java Reflection"></a>Java Reflection</h2><p>Reflection(反射)时Java被视为动态语言的关键,反射机制允许程序在执行期借助于Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法。</p><p>加载完类之后,在__堆内存的方法区__中就产生了一个__Class类型__的对象(一个类只有一个Class对象),这个Class对象就包含了__类的完整结构信息__,我们可以通过这个对象看到类的结构。这个对象就像一面镜子,透过这个镜子看到类的内部结构,所以形象的称之为__反射__。</p><h2 id="Java反射提供的功能"><a href="#Java反射提供的功能" class="headerlink" title="Java反射提供的功能"></a>Java反射提供的功能</h2><ol><li>运行时判断任意一个对象所属的类</li><li>运行时构造任意一个类的对象</li><li>运行时判断任意一个类所具有的成员变量和方法</li><li>运行时获取泛型信息</li><li>运行时调用任意一个对象的成员变量和方法</li><li>运行时处理注解</li><li>生成动态代理</li><li>…</li></ol><h4 id="优点:"><a href="#优点:" class="headerlink" title="优点:"></a>优点:</h4><p>可以实现动态创建对象和编译,体现出很大的灵活性</p><h4 id="缺点:"><a href="#缺点:" class="headerlink" title="缺点:"></a>缺点:</h4><p>对性能有影响。使用反射机制基本上是一种解释操作,我们可以告诉JVM,我们希望做什么并且它满足我们的要求。这类操作总是慢于直接执行相同的操作。</p><h2 id="反射相关的API"><a href="#反射相关的API" class="headerlink" title="反射相关的API"></a>反射相关的API</h2><p><strong>java.lang.Class</strong>:代表一个类</p><p><strong>java.lang.reflect.Field</strong>:代表类的成员变量</p><p><strong>java.lang.reflect.Method</strong>:代表类的成员方法</p><p><strong>java.lang.reflect.Constructor</strong>:代表类的构造器</p><h2 id="Class类"><a href="#Class类" class="headerlink" title="Class类"></a>Class类</h2><p>对象照镜子后可以得到的信息:某个类的__属性__、__方法__、和__构造器__、某个类到底实现了哪些__接口__。</p><p>对于每个类而言,JRE都为其保留了一个不变的Class类型的对象。一个Class对象包含了特定的某个结构的有关信息。</p><ul><li>Class本身是一个类</li><li>Class对象只能由系统建立</li><li>一个加载的类在JVM中只会有一个Class实例</li><li>一个Class对象对应的是一个加载到JVM中的一个.class文件</li><li>每个类的实例都会记得自己是有哪个Class实例所生成</li><li>通过Class可以完整地得到一个类中的所有被加载的结构</li><li>Class类是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的Class对象,</li></ul><h3 id="Class类的常用方法"><a href="#Class类的常用方法" class="headerlink" title="Class类的常用方法"></a>Class类的常用方法</h3><p><img src="/2022/10/22/Reflection/Class%E7%B1%BB%E5%B8%B8%E7%94%A8%E6%96%B9%E6%B3%95.jpg"></p><h3 id="获取Class类的实例"><a href="#获取Class类的实例" class="headerlink" title="获取Class类的实例"></a>获取Class类的实例</h3><p><img src="/2022/10/22/Reflection/%E8%8E%B7%E5%BE%97Class%E7%B1%BB%E7%9A%84%E5%AE%9E%E4%BE%8B.png"></p><h3 id="哪些类型可以有Class对象"><a href="#哪些类型可以有Class对象" class="headerlink" title="哪些类型可以有Class对象"></a>哪些类型可以有Class对象</h3><p>__class__:外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类</p><p>__interface__:接口</p><p><strong>[]</strong>:数组</p><p>__enum__:枚举</p><p>__annotation__:注解</p><p>__primitive type__:基本数据类型</p><p><strong>void</strong></p><h2 id="类加载内存分析"><a href="#类加载内存分析" class="headerlink" title="类加载内存分析"></a>类加载内存分析</h2><p>Java内存</p><ul><li><p>堆:存放new的对象和数组,可以被所有线程共享,不会存放别的对象引用</p></li><li><p>栈:</p><ul><li>存放基本变量类型(包括这个基本类型的数值)</li><li>引用对象的变量(存放这个引用在堆里面的具体地址)</li></ul></li><li><p>方法区:</p><ul><li>可以被所有的线程共享</li><li>包含了所有的class和static变量</li></ul></li></ul><h3 id="类的加载过程"><a href="#类的加载过程" class="headerlink" title="类的加载过程"></a>类的加载过程</h3><p><img src="/2022/10/22/Reflection/%E7%B1%BB%E5%8A%A0%E8%BD%BD%E8%BF%87%E7%A8%8B.jpg"></p><h4 id="加载"><a href="#加载" class="headerlink" title="加载"></a>加载</h4><p>将class文件字节码内容加载到__内存__中,并将这些__静态数据__转换成__方法区__的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象。</p><h4 id="链接"><a href="#链接" class="headerlink" title="链接"></a>链接</h4><p>将__Java类的二进制代码__合并到JVM的运行状态之中的过程</p><h5 id="验证"><a href="#验证" class="headerlink" title="验证"></a>验证</h5><p>确保加载的类信息符合JVM规范,没有安全方面的问题</p><h5 id="准备"><a href="#准备" class="headerlink" title="准备"></a>准备</h5><p>正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都在方法区中进行分配</p><h5 id="解析"><a href="#解析" class="headerlink" title="解析"></a>解析</h5><p>虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程</p><h4 id="初始化"><a href="#初始化" class="headerlink" title="初始化"></a>初始化</h4><p>执行类构造器方法的过程,类构造器方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。(类构造器是构造类信息的,不是构造该类对象的构造器)</p><p>当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先初始化父类</p><p>虚拟机会保证一个类的类构造器方法在多线程环境中被正确的加锁和同步</p><h4 id="什么时候会发生类的初始化"><a href="#什么时候会发生类的初始化" class="headerlink" title="什么时候会发生类的初始化"></a>什么时候会发生类的初始化</h4><h5 id="类的主动引用——一定会发生类的初始化"><a href="#类的主动引用——一定会发生类的初始化" class="headerlink" title="类的主动引用——一定会发生类的初始化"></a>类的主动引用——一定会发生类的初始化</h5><ul><li>当虚拟机启动,先初始化main方法所在的类</li><li>new一个类的对象</li><li>调用类的静态成员(除了final常量)和静态方法</li><li>使用java.lang.reflect包的方法对类进行反射调用</li><li>当初始化一个类时,如果其父类没有被初始化,则先会初始化它的父类</li></ul><h5 id="类的被动引用——不会发生类的初始化"><a href="#类的被动引用——不会发生类的初始化" class="headerlink" title="类的被动引用——不会发生类的初始化"></a>类的被动引用——不会发生类的初始化</h5><ul><li>当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化</li><li>通过数组定义类的引用,不会触发类的初始化</li><li>引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池了)</li></ul><h3 id="类加载器的作用"><a href="#类加载器的作用" class="headerlink" title="类加载器的作用"></a>类加载器的作用</h3><p>将class文件字节码内容加载到__内存__中,并将这些静态数据转换成方法区的运行时数据结构,然后再堆中生成一个代表这个类的Java.lang.Class对象,作为方法区中类数据的访问入口。</p><p>__类缓存__:标准的JavaSE类加载器可以按要求查找类,但一旦某个类被加载到类加载器中,他将维持加载(缓存)一段时间。不过JVM的垃圾回收机制可以回收这些Class对象。</p><p>JVM定义了如下类型的类加载器:</p><h4 id="引导类加载器"><a href="#引导类加载器" class="headerlink" title="引导类加载器"></a>引导类加载器</h4><p>用C++编写,是JVM自带的类加载器,负责__Java平台核心库__,用来装载核心类库。该加载器无法直接获取。</p><h4 id="扩展类加载器"><a href="#扩展类加载器" class="headerlink" title="扩展类加载器"></a>扩展类加载器</h4><p>负责__jre/lib/ext下__的jar包或java.ext.dirs指定目录下的jar包装入工作库</p><h4 id="系统类加载器"><a href="#系统类加载器" class="headerlink" title="系统类加载器"></a>系统类加载器</h4><p>负责__java-classpath__或者java.class.path所指的目录下的类与Jar包转入工作,是最常用的类加载器。</p><h2 id="获取运行时类的完整结构"><a href="#获取运行时类的完整结构" class="headerlink" title="获取运行时类的完整结构"></a>获取运行时类的完整结构</h2><p>通过反射获取运行时类的完整结构:<strong>Field Method Constructor Superclass Interface Annotation</strong></p><p>获得类名:<strong>getName() getSimpleName()</strong></p><p>获得属性:<strong>getFields()</strong>;只能找到public属性</p><p><strong>getDeclaredField()</strong>;获得所有属性</p><p>获得方法:<strong>getMethod()</strong>;获得本类和父类的所有public方法</p><p><strong>getDeclaredMethod()</strong>;获得本类的所有方法</p><p></p><p>获得指定的构造器:<strong>getConstructor()</strong> 获得public的构造方法</p><p> <strong>getDeclaredConstructor()</strong> 获得所有的构造方法</p><p>获得注解:<strong>getAnnotations()</strong></p><p>__getAnnotation()__获得指定的注解</p>]]></content>
<categories>
<category>Java</category>
</categories>
<tags>
<tag>Java</tag>
</tags>
</entry>
<entry>
<title>SpringDesignPattern</title>
<link href="/2022/10/22/SpringDesignPattern/"/>
<url>/2022/10/22/SpringDesignPattern/</url>
<content type="html"><![CDATA[<h2 id="Spring中的设计模式"><a href="#Spring中的设计模式" class="headerlink" title="Spring中的设计模式"></a>Spring中的设计模式</h2><h3 id="单例模式-Singleton-Pattern"><a href="#单例模式-Singleton-Pattern" class="headerlink" title="单例模式(Singleton Pattern)"></a>单例模式(Singleton Pattern)</h3><p><font color="red">Ensure a class has only one instance, and provide a global point of access to it.</font></p><p>确保一个类在任何情况下都绝对只有一个实例,并提供一个全局访问点。</p><h4 id="类加载"><a href="#类加载" class="headerlink" title="类加载"></a>类加载</h4><ol><li><p>创建类的实例,也就是new一个对象</p></li><li><p>访问某个类或接口的静态变量,或者对该静态变量赋值</p></li><li><p>调用类的静态方法</p></li><li><p>反射(Class.forName(“com.lyj.load”))</p></li><li><p>初始化一个类的子类(会首先初始化子类的父类)</p></li><li><p>JVM启动时标明的启动类,即文件名和类名相同的那个类 只有这6中情况才会导致类的类的初始化。</p></li></ol><h4 id="三种反射的区别"><a href="#三种反射的区别" class="headerlink" title="三种反射的区别"></a>三种反射的区别</h4><ul><li><p>类名.class:JVM将使用类装载器,将类装入内存(前提是:类还没有装入内存),不做类的初始化工作,返回Class的对象。</p></li><li><p>Class.forName(“类名字符串”):装入类,并做类的静态初始化,返回Class的对象。</p></li><li><p>实例对象.getClass():对类进行静态初始化、非静态初始化;返回引用运行时真正所指的对象(子对象的引用会赋给父对象的引用变量中)所属的类的Class的对象。</p></li></ul><h4 id="饿汉式单例模式"><a href="#饿汉式单例模式" class="headerlink" title="饿汉式单例模式"></a>饿汉式单例模式</h4><p>类加载的时候就立即初始化,并且创建单例对象。它是绝对的线程安全、在线程还没出现以前就实现了,不可能存在访问安全问题。</p><p>类加载的时候就初始化单例对象了,用不用都进行,浪费内存</p><ul><li>写法1</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//写法1</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">HungrySingleton</span> {<br> <span class="hljs-comment">//在类内部就初始化</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">HungrySingleton</span> <span class="hljs-variable">singleton</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">HungrySingleton</span>();<br> <span class="hljs-comment">//构造函数私有 外部不能访问</span><br> <span class="hljs-keyword">private</span> <span class="hljs-title function_">HungrySingleton</span><span class="hljs-params">()</span> {<br> System.out.println(<span class="hljs-string">"Hello,I am a HungrySingleton!"</span>);<br> }<br> <span class="hljs-comment">//全局访问点</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> HungrySingleton <span class="hljs-title function_">getSingleton</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">return</span> singleton;<br> }<br>}<br></code></pre></td></tr></table></figure><ul><li>写法2</li></ul><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Singleton</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">Singleton</span> <span class="hljs-variable">instance</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br> <span class="hljs-comment">//静态代码块</span><br> <span class="hljs-keyword">static</span> {<br> instance = <span class="hljs-keyword">new</span> <span class="hljs-title class_">Singleton</span>();<br> }<br> <span class="hljs-keyword">private</span> <span class="hljs-title function_">Singleton</span> <span class="hljs-params">()</span>{}<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Singleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">return</span> instance;<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="懒汉式单例模式"><a href="#懒汉式单例模式" class="headerlink" title="懒汉式单例模式"></a>懒汉式单例模式</h4><ul><li>简单懒汉写法</li></ul><p>多线程不安全</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//简单懒汉写法</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LazySingleton</span> {<br> <span class="hljs-comment">//静态类对象 一开始并不初始化</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-type">LazySingleton</span> <span class="hljs-variable">singleton</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br> <span class="hljs-comment">//构造函数私有 外界不能访问</span><br> <span class="hljs-keyword">private</span> <span class="hljs-title function_">LazySingleton</span><span class="hljs-params">()</span> {<br> }<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> LazySingleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">if</span> (singleton == <span class="hljs-literal">null</span>) {<br> singleton = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LazySingleton</span>();<br> }<br> <span class="hljs-keyword">return</span> singleton;<br>}<br></code></pre></td></tr></table></figure><ul><li>Double-Check写法</li></ul><p>阻塞大量线程,性能下降</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//Double-Check写法</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> LazySingleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">if</span> (singleton == <span class="hljs-literal">null</span>) {<br> <span class="hljs-keyword">synchronized</span> (LazySingleton.class) {<br> <span class="hljs-keyword">if</span> (singleton == <span class="hljs-literal">null</span>) {<br> singleton = <span class="hljs-keyword">new</span> <span class="hljs-title class_">LazySingleton</span>();<br> }<br> }<br> }<br> <span class="hljs-keyword">return</span> singleton;<br> }<br>}<br></code></pre></td></tr></table></figure><ul><li>内部类</li></ul><p>可以被反射和序列化破坏</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//内部类写法</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LazyInnerClassSingleton</span> {<br> <span class="hljs-comment">//私有的构造函数</span><br> <span class="hljs-keyword">private</span> <span class="hljs-title function_">LazyInnerClassSingleton</span><span class="hljs-params">()</span> {<br> }<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> LazyInnerClassSingleton <span class="hljs-title function_">getInstance</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">return</span> LazyHolder.singleton;<br> }<br> <span class="hljs-comment">//外部类加载时,静态内部类并不加载</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">LazyHolder</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">final</span> <span class="hljs-type">LazyInnerClassSingleton</span> <span class="hljs-variable">singleton</span> <span class="hljs-operator">=</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">LazyInnerClassSingleton</span>();<br> }<br>} <br></code></pre></td></tr></table></figure><h4 id="枚举式单例模式"><a href="#枚举式单例模式" class="headerlink" title="枚举式单例模式"></a>枚举式单例模式</h4><p><a href="https://blog.csdn.net/qq_36448587/article/details/107253971">https://blog.csdn.net/qq_36448587/article/details/107253971</a></p><p><img src="/2022/10/22/SpringDesignPattern/effectivejava.jpg"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">enum</span> <span class="hljs-title class_">EnumSingleton</span> {<br> <span class="hljs-comment">//枚举单例 </span><br> SINGLETON;<br>}<br></code></pre></td></tr></table></figure><h4 id="容器式单例模式"><a href="#容器式单例模式" class="headerlink" title="容器式单例模式"></a>容器式单例模式</h4><p>非线程安全</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">ContainerSingleton</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-title function_">ContainerSingleton</span><span class="hljs-params">()</span> {<br> }<br><span class="hljs-comment">//ConcurrentHashMap是HashMap的升级版 HashMap是线程不安全的 而ConcurrentHashMap是线程安全</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">static</span> Map<String, Object> ioc = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span><>();<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> Object <span class="hljs-title function_">getBean</span><span class="hljs-params">(String className)</span> {<br> <span class="hljs-keyword">synchronized</span> (ioc) {<br> <span class="hljs-keyword">if</span> (!ioc.containsKey(className)) {<br> <span class="hljs-type">Object</span> <span class="hljs-variable">o</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">try</span> {<br> o = Class.forName(className).newInstance();<br> ioc.put(className, o);<br> } <span class="hljs-keyword">catch</span> (Exception e) {<br> e.printStackTrace();<br> }<br> <span class="hljs-keyword">return</span> o;<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> ioc.get(className);<br> }<br> }<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="Spring中的单例"><a href="#Spring中的单例" class="headerlink" title="Spring中的单例"></a>Spring中的单例</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">protected</span> <T> T <span class="hljs-title function_">doGetBean</span><span class="hljs-params">(String name, <span class="hljs-meta">@Nullable</span> Class<T> requiredType, <span class="hljs-meta">@Nullable</span> Object[] args, <span class="hljs-type">boolean</span> typeCheckOnly)</span> <span class="hljs-keyword">throws</span> BeansException {<br> <span class="hljs-comment">//对Bean的名字进行处理 防止有非法字符等</span><br> <span class="hljs-type">String</span> <span class="hljs-variable">beanName</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.transformedBeanName(name);<br> <span class="hljs-comment">//调用getSingleton方法从获取bean单例</span><br> <span class="hljs-type">Object</span> <span class="hljs-variable">sharedInstance</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.getSingleton(beanName);<br> Object bean;<br> <span class="hljs-comment">//如果获取到了单例的bean</span><br> <span class="hljs-keyword">if</span> (sharedInstance != <span class="hljs-literal">null</span> && args == <span class="hljs-literal">null</span>) { <br> ......<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">if</span> (<span class="hljs-built_in">this</span>.isPrototypeCurrentlyInCreation(beanName)) {<br> <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">BeanCurrentlyInCreationException</span>(beanName);<br> }<br><br> <span class="hljs-type">BeanFactory</span> <span class="hljs-variable">parentBeanFactory</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.getParentBeanFactory();<br> <span class="hljs-keyword">if</span> (parentBeanFactory != <span class="hljs-literal">null</span> && !<span class="hljs-built_in">this</span>.containsBeanDefinition(beanName)){<br> ......<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> bean;<br> }<br> }<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map<String, Object> singletonObjects = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span>(<span class="hljs-number">256</span>);<br><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map<String, ObjectFactory<?>> singletonFactories = <span class="hljs-keyword">new</span> <span class="hljs-title class_">HashMap</span>(<span class="hljs-number">16</span>);<br><span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> Map<String, Object> earlySingletonObjects = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ConcurrentHashMap</span>(<span class="hljs-number">16</span>);<br><br><br><span class="hljs-keyword">protected</span> Object <span class="hljs-title function_">getSingleton</span><span class="hljs-params">(String beanName, <span class="hljs-type">boolean</span> allowEarlyReference)</span> {<br> <span class="hljs-comment">//首先从singletonObjects中获取单例对象(一级缓存) </span><br> <span class="hljs-type">Object</span> <span class="hljs-variable">singletonObject</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.singletonObjects.get(beanName);<br> <span class="hljs-comment">//如果没有获取到 并且此单例没有在创建 </span><br> <span class="hljs-comment">//isSingletonCurrentlyInCreation的作用判断这个beanName是不是正在创建,也就是判断是不是循环依赖了。</span><br> <span class="hljs-keyword">if</span> (singletonObject == <span class="hljs-literal">null</span> && <span class="hljs-built_in">this</span>.isSingletonCurrentlyInCreation(beanName)) {<br> <span class="hljs-comment">//从earlySingletonObjects中获取(二级缓存)</span><br> singletonObject = <span class="hljs-built_in">this</span>.earlySingletonObjects.get(beanName);<br> <span class="hljs-keyword">if</span> (singletonObject == <span class="hljs-literal">null</span> && allowEarlyReference) {<br> <span class="hljs-keyword">synchronized</span>(<span class="hljs-built_in">this</span>.singletonObjects) {<br> singletonObject = <span class="hljs-built_in">this</span>.singletonObjects.get(beanName);<br> <span class="hljs-keyword">if</span> (singletonObject == <span class="hljs-literal">null</span>) {<br> singletonObject = <span class="hljs-built_in">this</span>.earlySingletonObjects.get(beanName);<br> <span class="hljs-keyword">if</span> (singletonObject == <span class="hljs-literal">null</span>) {<br> <span class="hljs-comment">//从singletonFactories中获取</span><br> ObjectFactory<?> singletonFactory = (ObjectFactory)<span class="hljs-built_in">this</span>.singletonFactories.get(beanName);<br> <span class="hljs-keyword">if</span> (singletonFactory != <span class="hljs-literal">null</span>) {<br> singletonObject = singletonFactory.getObject();<br> <span class="hljs-built_in">this</span>.earlySingletonObjects.put(beanName, singletonObject);<br> <span class="hljs-built_in">this</span>.singletonFactories.remove(beanName);<br> }<br> }<br> }<br> }<br> }<br> }<br><br> <span class="hljs-keyword">return</span> singletonObject;<br> }<br></code></pre></td></tr></table></figure><p>单例的获取顺利是<strong>singletonObjects ——>earlySingletonObjects ——>singletonFactories</strong> 这样的三级层次。</p><p>在singletonObjects 中获取bean的时候,没有使用synchronized关键字,而在singletonFactories 和earlySingletonObjects 中的操作都是在synchronized代码块中完成的,正好和他们各自的数据类型对应,singletonObjects 使用的使用ConcurrentHashMap线程安全,而singletonFactories 和earlySingletonObjects 使用的是HashMap,线程不安全。</p><p>从字面意思来说:singletonObjects指单例对象的cache,singletonFactories指单例对象工厂的cache,earlySingletonObjects指提前曝光的单例对象的cache。以上三个cache构成了三级缓存,Spring就用这三级缓存巧妙的解决了循环依赖问题。</p><p><strong>Spring创建bean是线程安全的,不代表bean的使用是线程安全的。Spring中bean没有使用任何的线程安全措施,对单例且有状态的bean是不安全的,prototype多例的bean和无状态的bean是线程安全的。</strong></p><h3 id="适配器模式-Adapter-Pattern-x2F-Wrapper"><a href="#适配器模式-Adapter-Pattern-x2F-Wrapper" class="headerlink" title="适配器模式(Adapter Pattern/Wrapper)"></a>适配器模式(Adapter Pattern/Wrapper)</h3><p><font color="red">Convert the inface of a class into another interface clients expect.Adapter lets classes work together that couldn’t otherwise because of incompatible interface.</font></p><p>将一个类的接口变换成客户端所期待的另一种接口,从而使原本因接口不匹配而无法在一起工作的两个类能够在一起工作.</p><p><em><u>”没有什么问题是加一层不能解决的“</u></em></p><h6 id="优点"><a href="#优点" class="headerlink" title="优点"></a>优点</h6><ul><li>更好的<strong>复用性</strong>:系统需要使用现有的类,但是此类的接口不符合系统需要。那么通过适配器模式就可以让这些功能得到更好的复用(原有的功能不需要再实现一遍)</li><li>更好的<strong>扩展性</strong>:在实现适配器功能的时候,可以调用自己开发的功能,从而自然地扩展系统的功能(不用修改原有代码)</li></ul><h6 id="缺点"><a href="#缺点" class="headerlink" title="缺点"></a>缺点</h6><p>过多的使用适配器,会使系统非常凌乱,不易整体进把握。</p><h4 id="类适配器"><a href="#类适配器" class="headerlink" title="类适配器"></a>类适配器</h4><p><img src="/2022/10/22/SpringDesignPattern/%E7%B1%BB%E9%80%82%E9%85%8D%E5%99%A8.jpg"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Target</span> {<br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">request</span><span class="hljs-params">()</span>;<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Adaptee</span> {<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">specificRequest</span><span class="hljs-params">()</span> {<br> System.out.println(<span class="hljs-string">"specificRequest..."</span>);<br> }<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Adapter</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">Adaptee</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Target</span>{<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">request</span><span class="hljs-params">()</span> {<br> specificRequest();<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="对象适配器"><a href="#对象适配器" class="headerlink" title="对象适配器"></a>对象适配器</h4><p><img src="/2022/10/22/SpringDesignPattern/%E5%AF%B9%E8%B1%A1%E9%80%82%E9%85%8D%E5%99%A8.jpg"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">Target</span> {<br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">request</span><span class="hljs-params">()</span>;<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Adaptee</span> {<br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">specificRequest</span><span class="hljs-params">()</span> {<br> System.out.println(<span class="hljs-string">"specificRequest..."</span>);<br> }<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Adapter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">Target</span> {<br> <span class="hljs-keyword">private</span> Adaptee adaptee;<br><br> <span class="hljs-keyword">public</span> <span class="hljs-title function_">Adapter</span><span class="hljs-params">(Adaptee adaptee)</span> {<br> <span class="hljs-built_in">this</span>.adaptee = adaptee;<br> }<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">request</span><span class="hljs-params">()</span> {<br> adaptee.specificRequest();<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="缺省适配器"><a href="#缺省适配器" class="headerlink" title="缺省适配器"></a>缺省适配器</h4><p>当不需要实现一个接口所提供的所有方法时,可先设计一个抽象类实现该接口,并为接口中的每个方法提供一个默认实现(空方法),该抽象类可以有选择性的覆盖父类的某些方法来实现需求。</p><p><img src="/2022/10/22/SpringDesignPattern/%E7%BC%BA%E7%9C%81%E9%80%82%E9%85%8D%E5%99%A8.jpg"></p><h4 id="双向适配器"><a href="#双向适配器" class="headerlink" title="双向适配器"></a>双向适配器</h4><p>在对象适配器中如果同时包含目标类和适配者类的引用,适配者可以通过它调用目标类中的方法,目标类也可以通过它调用适配者类中的方法</p><h4 id="Spring中的适配器模式"><a href="#Spring中的适配器模式" class="headerlink" title="Spring中的适配器模式"></a>Spring中的适配器模式</h4><p>SpringAOP和SpringMVC中都有用到适配器模式。</p><p>SpringAOP 中的 AdvisorAdapter 类,它有三个实现类 MethodBeforeAdviceAdapter、AfterReturningAdviceAdapter 和ThrowsAdviceAdapter。</p><p><img src="/2022/10/22/SpringDesignPattern/%E9%80%82%E9%85%8D%E5%99%A8%E5%9B%BE.png"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">//springframework-->aop-->framework-->adapter包</span><br><br><span class="hljs-comment">//Target</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">AdvisorAdapter</span> {<br> <span class="hljs-type">boolean</span> <span class="hljs-title function_">supportsAdvice</span><span class="hljs-params">(Advice var1)</span>;<br><br> MethodInterceptor <span class="hljs-title function_">getInterceptor</span><span class="hljs-params">(Advisor var1)</span>;<br>}<br><span class="hljs-comment">//Adapter</span><br><span class="hljs-comment">//适配MethodBeforeAdviceInterceptor 接口getInterceptor(Advisor advisor)</span><br><span class="hljs-comment">//进行一层包装 对外提供想要的方法</span><br><span class="hljs-keyword">class</span> <span class="hljs-title class_">MethodBeforeAdviceAdapter</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">AdvisorAdapter</span>, Serializable {<br> MethodBeforeAdviceAdapter() {<br> }<br><span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-type">boolean</span> <span class="hljs-title function_">supportsAdvice</span><span class="hljs-params">(Advice advice)</span> {<br> <span class="hljs-keyword">return</span> advice <span class="hljs-keyword">instanceof</span> MethodBeforeAdvice;<br> }<br><span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> MethodInterceptor <span class="hljs-title function_">getInterceptor</span><span class="hljs-params">(Advisor advisor)</span> {<br> <span class="hljs-type">MethodBeforeAdvice</span> <span class="hljs-variable">advice</span> <span class="hljs-operator">=</span> (MethodBeforeAdvice)advisor.getAdvice();<br> <span class="hljs-keyword">return</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">MethodBeforeAdviceInterceptor</span>(advice);<br> }<br>}<br><span class="hljs-comment">//Adaptee</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">MethodBeforeAdvice</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">BeforeAdvice</span> {<br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">before</span><span class="hljs-params">(Method var1, Object[] var2, <span class="hljs-meta">@Nullable</span> Object var3)</span> <span class="hljs-keyword">throws</span> Throwable;<br>}<br><span class="hljs-comment">//client </span><br><span class="hljs-comment">//调用AdvisorAdapter对外暴露的接口</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">DefaultAdvisorAdapterRegistry</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">AdvisorAdapterRegistry</span>, Serializable {<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">final</span> List<AdvisorAdapter> adapters = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>(<span class="hljs-number">3</span>);<br><br> <span class="hljs-keyword">public</span> <span class="hljs-title function_">DefaultAdvisorAdapterRegistry</span><span class="hljs-params">()</span> {<br> <span class="hljs-built_in">this</span>.registerAdvisorAdapter(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MethodBeforeAdviceAdapter</span>());<br> <span class="hljs-built_in">this</span>.registerAdvisorAdapter(<span class="hljs-keyword">new</span> <span class="hljs-title class_">AfterReturningAdviceAdapter</span>());<br> <span class="hljs-built_in">this</span>.registerAdvisorAdapter(<span class="hljs-keyword">new</span> <span class="hljs-title class_">ThrowsAdviceAdapter</span>());<br> }<br> <span class="hljs-keyword">public</span> Advisor <span class="hljs-title function_">wrap</span><span class="hljs-params">(Object adviceObject)</span> <span class="hljs-keyword">throws</span> UnknownAdviceTypeException {<br> ......<br> }<br><span class="hljs-comment">// 获取适配器对应的所有的拦截器</span><br> <span class="hljs-keyword">public</span> MethodInterceptor[] getInterceptors(Advisor advisor) <span class="hljs-keyword">throws</span> UnknownAdviceTypeException {<br> List<MethodInterceptor> interceptors = <span class="hljs-keyword">new</span> <span class="hljs-title class_">ArrayList</span>(<span class="hljs-number">3</span>);<br> <span class="hljs-type">Advice</span> <span class="hljs-variable">advice</span> <span class="hljs-operator">=</span> advisor.getAdvice();<br> <span class="hljs-keyword">if</span> (advice <span class="hljs-keyword">instanceof</span> MethodInterceptor) {<br> interceptors.add((MethodInterceptor)advice);<br> }<br><br> <span class="hljs-type">Iterator</span> <span class="hljs-variable">var4</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.adapters.iterator();<br><br> <span class="hljs-keyword">while</span>(var4.hasNext()) {<br> <span class="hljs-type">AdvisorAdapter</span> <span class="hljs-variable">adapter</span> <span class="hljs-operator">=</span> (AdvisorAdapter)var4.next();<br> <span class="hljs-keyword">if</span> (adapter.supportsAdvice(advice)) {<br> interceptors.add(adapter.getInterceptor(advisor));<br> }<br> }<br><br> <span class="hljs-keyword">if</span> (interceptors.isEmpty()) {<br> <span class="hljs-keyword">throw</span> <span class="hljs-keyword">new</span> <span class="hljs-title class_">UnknownAdviceTypeException</span>(advisor.getAdvice());<br> } <span class="hljs-keyword">else</span> {<br> <span class="hljs-keyword">return</span> (MethodInterceptor[])interceptors.toArray(<span class="hljs-keyword">new</span> <span class="hljs-title class_">MethodInterceptor</span>[<span class="hljs-number">0</span>]);<br> }<br> }<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">registerAdvisorAdapter</span><span class="hljs-params">(AdvisorAdapter adapter)</span> {<br> <span class="hljs-built_in">this</span>.adapters.add(adapter);<br> }<br>}<br><br></code></pre></td></tr></table></figure><h3 id="模板方法模式-Template-Method-Pattern"><a href="#模板方法模式-Template-Method-Pattern" class="headerlink" title="模板方法模式(Template Method Pattern)"></a>模板方法模式(Template Method Pattern)</h3><p><font color="red">Define the skeleton of an algorithm in an operation,deferring some steps to subclasses.Template Method lets subclass redefine certain steps of an algorithm without changing the algorithm’s structure.</font></p><p> 定义一个操作中的算法框架,而将一些步骤延迟到子类中。使得子类可以不改变一个算法的结构即可以重定义该算法的某些特定步骤。</p><h6 id="优点-1"><a href="#优点-1" class="headerlink" title="优点"></a>优点</h6><ul><li><p>封装了不变部分,扩展可变部分。</p></li><li><p>在父类中提取了公共的部分代码,便于代码复用。</p></li><li><p>部分方法是由子类实现的,因此子类可以通过扩展方式增加相应的功能,符合开闭原则。</p></li></ul><h6 id="缺点-1"><a href="#缺点-1" class="headerlink" title="缺点"></a>缺点</h6><ul><li>对每个不同的实现都需要定义一个子类,这会导致类的个数增加,系统更加庞大,设计也更加抽象,间接地增加了系统实现的复杂度。</li><li>父类中的抽象方法由子类实现,子类执行的结果会影响父类的结果,这导致一种反向的控制结构,它提高了代码阅读的难度。</li><li>由于继承关系自身的缺点,如果父类添加新的抽象方法,则所有子类都要改一遍。</li></ul><p><img src="/2022/10/22/SpringDesignPattern/%E6%A8%A1%E6%9D%BF%E6%96%B9%E6%B3%95%E6%A8%A1%E5%BC%8F.jpg"></p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AbstractClass</span> {<br> <span class="hljs-comment">// 共同的且繁琐的操作</span><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">baseOperation</span><span class="hljs-params">()</span> {<br> <span class="hljs-comment">// do something</span><br> }<br><br> <span class="hljs-comment">// 由子类定制的操作</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">customOperation</span><span class="hljs-params">()</span>;<br><br> <span class="hljs-comment">// 模板方法定义的框架</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">templateMethod</span><span class="hljs-params">()</span> {<br> <span class="hljs-comment">/**</span><br><span class="hljs-comment"> * 调用基本方法,完成固定逻辑</span><br><span class="hljs-comment"> */</span><br> baseOperation();<br> customOperation();<br> }<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomClass1</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractClass</span>{<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">customOperation</span><span class="hljs-params">()</span> {<br> <span class="hljs-comment">// 具体模板1 业务逻辑</span><br> System.out.println(<span class="hljs-string">"具体模板1:customOperation()"</span>);<br> }<br>}<br><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">CustomClass2</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractClass</span>{<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">customOperation</span><span class="hljs-params">()</span> {<br> <span class="hljs-comment">// 具体模板2 业务逻辑</span><br> System.out.println(<span class="hljs-string">"具体模板2:customOperation()"</span>);<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="回调方式"><a href="#回调方式" class="headerlink" title="回调方式"></a>回调方式</h4><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">CallBack</span> {<br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">customOperation</span><span class="hljs-params">()</span>;<br>}<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">SubCallBack</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">CallBack</span>{<br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">customOperation</span><span class="hljs-params">()</span> {<br> System.out.println(<span class="hljs-string">"callback operation1..."</span>);<br> }<br>}<br><span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">Template</span> {<br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">baseOperation</span><span class="hljs-params">()</span>{<br> System.out.println(<span class="hljs-string">"模板类公共操作"</span>);<br> }<br><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">templateMethod</span><span class="hljs-params">(CallBack callback)</span>{<br> baseOperation();<br> callback.customOperation();<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="钩子函数"><a href="#钩子函数" class="headerlink" title="钩子函数"></a>钩子函数</h4><p>设计钩子方法的主要目的是干预模板方法的执行流程,使得控制行为流程更加灵活,更符合实际业务的需求。钩子方法的返回值一般为适合条件分支语句的返回值(如boolean、int等)。子类可以自行选择重写。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-keyword">public</span> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">AbstractBasePay</span> <span class="hljs-keyword">implements</span> <span class="hljs-title class_">BasePay</span> {<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">public</span> <span class="hljs-keyword">final</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">mobilePay</span><span class="hljs-params">()</span> {<br> <span class="hljs-comment">// 钩子函数</span><br> <span class="hljs-keyword">if</span> (isCheckAuth()) {<br> checkAuth();<br> }<br> checkParam();<br> checkRisk();<br> channlePay();<br> }<br><br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">checkParam</span><span class="hljs-params">()</span> {<br> System.out.println(<span class="hljs-string">"检查参数"</span>);<br> }<br> <br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">checkAuth</span><span class="hljs-params">()</span> {<br> System.out.println(<span class="hljs-string">"支付权限校验"</span>);<br> }<br> <br> <span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">checkRisk</span><span class="hljs-params">()</span> {<br> System.out.println(<span class="hljs-string">"风控校验"</span>);<br> }<br> <br> <span class="hljs-comment">//渠道支付</span><br> <span class="hljs-keyword">abstract</span> <span class="hljs-keyword">void</span> <span class="hljs-title function_">channlePay</span><span class="hljs-params">()</span>;<br> <br> <span class="hljs-comment">//钩子函数,子类可以覆写,来选择手开启支付权限校验 默认不开启</span><br> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isCheckAuth</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">true</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><code class="hljs java"><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">class</span> <span class="hljs-title class_">EBankChannelPay</span> <span class="hljs-keyword">extends</span> <span class="hljs-title class_">AbstractBasePay</span>{<br><br> <span class="hljs-meta">@Override</span><br> <span class="hljs-keyword">void</span> <span class="hljs-title function_">channlePay</span><span class="hljs-params">()</span> {<br> System.out.println(<span class="hljs-string">"网银支付"</span>);<br> }<br> <br> <span class="hljs-type">boolean</span> <span class="hljs-title function_">isCheckAuth</span><span class="hljs-params">()</span> {<br> <span class="hljs-keyword">return</span> <span class="hljs-literal">false</span>;<br> }<br>}<br></code></pre></td></tr></table></figure><h4 id="Spring中的模板方法模式"><a href="#Spring中的模板方法模式" class="headerlink" title="Spring中的模板方法模式"></a>Spring中的模板方法模式</h4><p>在spring源码中使用到模板模式的有以下几类(<strong>在Spring中大多数模板方式都是行为接口的定义:Callback</strong>):</p><ul><li><strong>RestTemplate Http Restful接口请求模板</strong></li><li><strong>AsyncRestTemplate 异步Http Restful接口请求模板</strong></li><li><strong>JdbcTemplate JDBC关系型数据库操作模板</strong></li><li><strong>HibernateTemplate Hibernate关系型数据库操作模板</strong></li><li><strong>JmsTemplate 消息队列模板</strong></li><li><strong>TransactionTemplate 编程式事务模板</strong></li></ul><p>带有Template的基本都是模板方法模式。</p><h5 id="JdbcTemplate"><a href="#JdbcTemplate" class="headerlink" title="JdbcTemplate"></a>JdbcTemplate</h5><p>JdbcTemplate是JDBC核心软件包中的中心类。它简化了JDBC的使用,并有助于避免常见错误。它执行核心JDBC工作流程,留下应用程序代码以提供SQL并提取结果</p><p>spring通过JdbcTemplate提供原生的JDBC SQL语句执行操作。JdbcTemplate和JdbcOperations均位于org.springframework.jdbc.core下.</p><p>JdbcOperations提供最基本的SQL方法执行接口,JdbcTemplate负责模板方法调用。</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">ConnectionCallback</span><T> {<br> <span class="hljs-meta">@Nullable</span><br> T <span class="hljs-title function_">doInConnection</span><span class="hljs-params">(Connection var1)</span> <span class="hljs-keyword">throws</span> SQLException, DataAccessException;<br>}<br><br><span class="hljs-meta">@Nullable</span><br> <span class="hljs-keyword">public</span> <T> T <span class="hljs-title function_">execute</span><span class="hljs-params">(ConnectionCallback<T> action)</span> <span class="hljs-keyword">throws</span> DataAccessException {<br> Assert.notNull(action, <span class="hljs-string">"Callback object must not be null"</span>);<br> <span class="hljs-type">Connection</span> <span class="hljs-variable">con</span> <span class="hljs-operator">=</span> DataSourceUtils.getConnection(<span class="hljs-built_in">this</span>.obtainDataSource());<br><br> Object var10;<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-type">Connection</span> <span class="hljs-variable">conToUse</span> <span class="hljs-operator">=</span> <span class="hljs-built_in">this</span>.createConnectionProxy(con);<br> var10 = action.doInConnection(conToUse);<br> } <span class="hljs-keyword">catch</span> (SQLException var8) {<br> <span class="hljs-type">String</span> <span class="hljs-variable">sql</span> <span class="hljs-operator">=</span> getSql(action);<br> DataSourceUtils.releaseConnection(con, <span class="hljs-built_in">this</span>.getDataSource());<br> con = <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">throw</span> <span class="hljs-built_in">this</span>.translateException(<span class="hljs-string">"ConnectionCallback"</span>, sql, var8);<br> } <span class="hljs-keyword">finally</span> {<br> DataSourceUtils.releaseConnection(con, <span class="hljs-built_in">this</span>.getDataSource());<br> }<br><br> <span class="hljs-keyword">return</span> var10;<br> }<br></code></pre></td></tr></table></figure><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@FunctionalInterface</span><br><span class="hljs-keyword">public</span> <span class="hljs-keyword">interface</span> <span class="hljs-title class_">StatementCallback</span><T> {<br> <span class="hljs-meta">@Nullable</span><br> T <span class="hljs-title function_">doInStatement</span><span class="hljs-params">(Statement var1)</span> <span class="hljs-keyword">throws</span> SQLException, DataAccessException;<br>}<br><br><span class="hljs-meta">@Nullable</span><br> <span class="hljs-keyword">public</span> <T> T <span class="hljs-title function_">execute</span><span class="hljs-params">(StatementCallback<T> action)</span> <span class="hljs-keyword">throws</span> DataAccessException {<br> Assert.notNull(action, <span class="hljs-string">"Callback object must not be null"</span>);<br> <span class="hljs-comment">//1.获得连接 </span><br> <span class="hljs-type">Connection</span> <span class="hljs-variable">con</span> <span class="hljs-operator">=</span> DataSourceUtils.getConnection(<span class="hljs-built_in">this</span>.obtainDataSource());<br> <span class="hljs-type">Statement</span> <span class="hljs-variable">stmt</span> <span class="hljs-operator">=</span> <span class="hljs-literal">null</span>;<br><br> Object var11;<br> <span class="hljs-keyword">try</span> {<br> <span class="hljs-comment">//2.创建语句</span><br> stmt = con.createStatement();<br> <span class="hljs-built_in">this</span>.applyStatementSettings(stmt);<br> <span class="hljs-comment">//3.执行语句</span><br> <span class="hljs-type">T</span> <span class="hljs-variable">result</span> <span class="hljs-operator">=</span> action.doInStatement(stmt);<br> <span class="hljs-built_in">this</span>.handleWarnings(stmt);<br> var11 = result;<br> } <span class="hljs-keyword">catch</span> (SQLException var9) {<br> <span class="hljs-type">String</span> <span class="hljs-variable">sql</span> <span class="hljs-operator">=</span> getSql(action);<br> JdbcUtils.closeStatement(stmt);<br> stmt = <span class="hljs-literal">null</span>;<br> DataSourceUtils.releaseConnection(con, <span class="hljs-built_in">this</span>.getDataSource());<br> con = <span class="hljs-literal">null</span>;<br> <span class="hljs-keyword">throw</span> <span class="hljs-built_in">this</span>.translateException(<span class="hljs-string">"StatementCallback"</span>, sql, var9);<br> } <span class="hljs-keyword">finally</span> {<br> <span class="hljs-comment">//4.关闭语句</span><br> JdbcUtils.closeStatement(stmt);<br> <span class="hljs-comment">//5.释放连接</span><br> DataSourceUtils.releaseConnection(con, <span class="hljs-built_in">this</span>.getDataSource());<br> }<br> <span class="hljs-keyword">return</span> var11;<br> }<br></code></pre></td></tr></table></figure><p>子类在实现该接口时,需要实现回调接口中的<code>doInStatement</code>方法,即执行SQL的相关逻辑,这样模板方法就会因doInstatement的不同逻辑而呈现不同的功能,如查询、更新等。</p>]]></content>
<categories>
<category>Spring</category>
<category>Spring中的设计模式</category>
</categories>
<tags>
<tag>Spring</tag>
</tags>
</entry>
</search>