-
Notifications
You must be signed in to change notification settings - Fork 2
/
atom.xml
385 lines (234 loc) · 718 KB
/
atom.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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>along101 个人博客</title>
<link href="/atom.xml" rel="self"/>
<link href="http://along101.site/"/>
<updated>2018-07-20T03:10:04.413Z</updated>
<id>http://along101.site/</id>
<author>
<name>along101</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>springMvc Controller方法参数注解不被继承问题</title>
<link href="http://along101.site/2018/07/19/springMvc-controller-param/"/>
<id>http://along101.site/2018/07/19/springMvc-controller-param/</id>
<published>2018-07-19T14:20:13.000Z</published>
<updated>2018-07-20T03:10:04.413Z</updated>
<content type="html"><![CDATA[<h1 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h1><p>spring-cloud-openfeign 很方便的将http rest服务调用转换为基于接口的rpc调用。<br>服务端和客户端多引用相同的Rest定义接口,服务端使用RestController实现该接口,客户端通过Feign生成该接口的代理,就能保证服务端和客户端的协议。<br>但是springMvc有个缺陷,RestController继承的接口能将接口类和方法上的注解都继承过来,<strong>但是方法参数的注解却无法继承</strong>。<br>本文提供了一种解决该问题的方案,非侵入试的巧妙的解决了该问题。</p><a id="more"></a><h1 id="问题还原"><a href="#问题还原" class="headerlink" title="问题还原"></a>问题还原</h1><h2 id="hello-service-api"><a href="#hello-service-api" class="headerlink" title="hello-service-api"></a>hello-service-api</h2><p>首先新建一个公共接口工程<code>hello-service-api</code></p><blockquote><p>可以参考github:<a href="https://github.com/along101/spring-boot-test/tree/master/feign-test/hello-service-api" target="_blank" rel="noopener">hello-service-api</a></p></blockquote><p>新建两个公共的接口,包含springMvc注解:<br><code>HelloService1</code><br><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><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/test1"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">HelloService1</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"/hello1"</span>, method = RequestMethod.GET)</span><br><span class="line"> <span class="function">String <span class="title">hello</span><span class="params">(@RequestParam(<span class="string">"name"</span>)</span> String name)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"/hello2"</span>, method = RequestMethod.GET)</span><br><span class="line"> <span class="function">User <span class="title">hello</span><span class="params">(@RequestParam(<span class="string">"name"</span>)</span> String name, @<span class="title">RequestParam</span><span class="params">(<span class="string">"age"</span>)</span> Integer age)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"/hello3"</span>, method = RequestMethod.POST)</span><br><span class="line"> <span class="function">String <span class="title">hello</span><span class="params">(@RequestBody User user)</span></span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>HelloService2</code><br><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><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/test2"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">HelloService2</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"/hello1"</span>, method = RequestMethod.GET)</span><br><span class="line"> <span class="function">String <span class="title">hello</span><span class="params">(@RequestParam(<span class="string">"name"</span>)</span> String name)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"/hello2"</span>, method = RequestMethod.GET)</span><br><span class="line"> <span class="function">User <span class="title">hello</span><span class="params">(@RequestParam(<span class="string">"name"</span>)</span> String name, @<span class="title">RequestParam</span><span class="params">(<span class="string">"age"</span>)</span> Integer a)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"/hello3"</span>, method = RequestMethod.POST)</span><br><span class="line"> <span class="function">String <span class="title">hello</span><span class="params">(@RequestBody User user)</span></span>;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2 id="hello-service-problem"><a href="#hello-service-problem" class="headerlink" title="hello-service-problem"></a>hello-service-problem</h2><p>新建spring boot工程<code>hello-service-problem</code>,依赖<code>hello-service-api</code></p><blockquote><p>可以参考github:<a href="https://github.com/along101/spring-boot-test/tree/master/feign-test/hello-service-problem" target="_blank" rel="noopener">hello-service-problem</a></p></blockquote><p>新建两个<code>RestController</code>,分别实现<code>HelloService1</code>、<code>HelloService2</code>两个接口。</p><p><code>HelloController1</code>,方法参数上没有加注解<br><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><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController1</span> <span class="keyword">implements</span> <span class="title">HelloService1</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">(String name)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello1 "</span> + name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">hello</span><span class="params">(String name, Integer a)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> User(name, a);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">(User user)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello1 "</span> + user.getName() + <span class="string">", "</span> + user.getAge();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>HelloController2</code>,方法参数上加了注解<br><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><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController2</span> <span class="keyword">implements</span> <span class="title">HelloService2</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">(@RequestParam(<span class="string">"name"</span>)</span> String name) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello2 "</span> + name;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> User <span class="title">hello</span><span class="params">(@RequestParam(<span class="string">"name"</span>)</span> String name, @<span class="title">RequestParam</span><span class="params">(<span class="string">"age"</span>)</span> Integer a) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> User(name, a);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">(@RequestBody User user)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello2 "</span> + user.getName() + <span class="string">", "</span> + user.getAge();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>启动工程,在浏览器中输入url测试两个接口:</p><p><a href="http://localhost:8081/test1/hello2?name=along&age=12" target="_blank" rel="noopener">http://localhost:8081/test1/hello2?name=along&age=12</a><br>结果:<br><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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"along"</span>,</span><br><span class="line"> <span class="attr">"age"</span>: <span class="literal">null</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><a href="http://localhost:8081/test2/hello2?name=along&age=12" target="_blank" rel="noopener">http://localhost:8081/test2/hello2?name=along&age=12</a><br>结果:<br><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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"along"</span>,</span><br><span class="line"> <span class="attr">"age"</span>: <span class="number">12</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>为什么第一个测试的name有值,而age为null呢?<br>我们看到<code>HelloController1.hello(String name, Integer a)</code>方法参数没有注解,因为没有注解,spring根据参数名称从请求参数中找值,name找到了,a没有找到,所有name有值,age为null。</p><p>第二个测试,<code>HelloController2.hello(@RequestParam("name") String name, @RequestParam("age") Integer a)</code>方法参数都有注解,spring就根据注解从请求参数中找值,都能找到,所有都不为空。</p><p>问题来了,我们继承了接口,接口方法参数上的注解没有继承过来,还要我们自己再写一遍,如果接口协议改了,实现类上的注解忘记改就悲剧了。</p><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><p>问题是很明确了,如何解决呢?先看看springMvc原理,搞清楚一个请求过来,是如何反射到方法上的。</p><p>网上找到一个springMvc的原理图,描述的比较清晰:</p><p><img src="/2018/07/19/springMvc-controller-param/springMvc.png" alt=""></p><p>简单的描述下,几个核心类是<code>DispatcherServlet</code>、<code>HandlerMapping</code>、<code>HandlerAdapter</code>、<code>HandlerMethod</code>、<code>ModelAndView</code>、<code>ViewResolver</code>。</p><ul><li><code>DispatcherServlet</code> 处理请求的servlet,前端控制器,整个流程控制的中心,控制其它组件执行,统一调度</li><li><code>HandlerMapping</code> 处理映射器,根据请匹配处理器</li><li><code>HandlerAdapter</code> 处理适配器,将请求适配到处理器上</li><li><code>HandlerMethod</code> 具体的处理器,一般是Controller方法信息的封装</li><li><code>ModelAndView</code> 模型和视图,传递处理结果</li><li><code>ViewResolver</code> 视图解析器</li></ul><p>springMvc启动的时候,会通过配置或者扫描注解将这些核心类实例化,组装到<code>DispatcherServlet</code>中。</p><p>客户端发起请求springMvc主要处理的过程如下:</p><ol><li><code>DispatcherServlet</code>根据请求在所有的<code>HandlerMapping</code>中找到匹配的<code>HandlerMethod</code></li><li><code>DispatcherServlet</code>根据找到的<code>HandlerMethod</code>,找到所支持的<code>HandlerAdapter</code></li><li><code>HandlerAdapter</code>的主要实现类是<code>RequestMappingHandlerAdapter</code>,负责从request中解析<code>HandlerMethod</code>所需要的参数,然后反射调用<code>HandlerMethod</code>中的方法,再处理返回值封装为<code>ModelAndView</code>。</li><li>通过<code>ViewResolver</code>解析<code>ModelAndView</code>,处理请求返回</li></ol><p><code>HandlerMethod</code>中存放了调用发放的信息,属性有:<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HandlerMethod</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="comment">/** Logger that is available to subclasses */</span></span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">final</span> Log logger = LogFactory.getLog(getClass());</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Object bean;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> BeanFactory beanFactory;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Class<?> beanType;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Method method;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Method bridgedMethod;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> MethodParameter[] parameters;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> HandlerMethod resolvedFromHandlerMethod;</span><br><span class="line">。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>属性<code>MethodParameter[] parameters</code>是方法参数数组,存放每个方法参数的信息,包括参数注解。</p><p>springMvc就是根据<code>MethodParameter</code>从request中获取信息构造方法参数数组的。</p><p>既然springMvc没有将参数注解从父接口中集成过来,我们可以自己处理。</p><p>spring生命周期中的各个阶段,我们都可以进行扩展,那么,我们可以在spring bean装配完后,修改所有<code>HandlerMethod</code>中的<code>parameters</code>,找到所继承的接口重新填充就可以了。</p><h1 id="解决方案"><a href="#解决方案" class="headerlink" title="解决方案"></a>解决方案</h1><p>通过调试springMvc的源代码,spring启动时,将<code>HandlerMapping</code>的实现类<code>RequestMappingHandlerMapping</code>实例化为一个bean注册到Context中,通过<code>getHandlerMethods</code>方法可以获取到所有的<code>HandlerMethod</code>。</p><p>我们只要实现一个Bean的后置处理器,修改<code>RequestMappingHandlerMapping</code>中的所有<code>HandlerMethod</code>即可,代码如下:<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HandlerMappingPostProcessor</span> <span class="keyword">implements</span> <span class="title">BeanPostProcessor</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">postProcessBeforeInitialization</span><span class="params">(Object bean, String beanName)</span> <span class="keyword">throws</span> BeansException </span>{</span><br><span class="line"> <span class="keyword">return</span> bean;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Object <span class="title">postProcessAfterInitialization</span><span class="params">(Object bean, String beanName)</span> <span class="keyword">throws</span> BeansException </span>{</span><br><span class="line"> <span class="comment">//找到RequestMappingHandlerMapping</span></span><br><span class="line"> <span class="keyword">if</span> (RequestMappingHandlerMapping.class.isAssignableFrom(ClassUtils.getUserClass(bean))) {</span><br><span class="line"> RequestMappingHandlerMapping mapping = (RequestMappingHandlerMapping) bean;</span><br><span class="line"> <span class="comment">//获取所有的HandlerMethod</span></span><br><span class="line"> Map<RequestMappingInfo, HandlerMethod> maps = mapping.getHandlerMethods();</span><br><span class="line"> <span class="keyword">for</span> (HandlerMethod handlerMethod : maps.values()) {</span><br><span class="line"> <span class="comment">//获取所有的MethodParameter</span></span><br><span class="line"> MethodParameter[] parameters = handlerMethod.getMethodParameters();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < parameters.length; i++) {</span><br><span class="line"> MethodParameter parameter = parameters[i];</span><br><span class="line"> Annotation[] parameterAnnotation = parameter.getParameterAnnotations();</span><br><span class="line"> <span class="comment">//找到接口方法</span></span><br><span class="line"> Method superMethod = findInterfaceMethod(parameter.getMethod());</span><br><span class="line"> <span class="keyword">if</span> (superMethod == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//接口方法参数注解</span></span><br><span class="line"> Annotation[][] superMethodParameterAnnotations = superMethod.getParameterAnnotations();</span><br><span class="line"> Annotation[] superParameterAnnotation = superMethodParameterAnnotations[parameter.getParameterIndex()];</span><br><span class="line"> <span class="comment">//合并自身方法注解和接口方法注解,形成新的注解数组</span></span><br><span class="line"> Annotation[] newParameterAnnotation = <span class="keyword">new</span> Annotation[parameterAnnotation.length + superParameterAnnotation.length];</span><br><span class="line"> System.arraycopy(parameterAnnotation, <span class="number">0</span>, newParameterAnnotation, <span class="number">0</span>, parameterAnnotation.length);</span><br><span class="line"> System.arraycopy(superParameterAnnotation, <span class="number">0</span>, newParameterAnnotation, parameterAnnotation.length, superParameterAnnotation.length);</span><br><span class="line"> <span class="comment">//构造新的MethodParameter,由于SynthesizingMethodParameter参数没有公有的设置注解方法,只能新建一个</span></span><br><span class="line"> <span class="keyword">if</span> (parameter <span class="keyword">instanceof</span> SynthesizingMethodParameter) {</span><br><span class="line"> MethodParameter newParameter = <span class="keyword">new</span> NewHandlerMethodParameter((SynthesizingMethodParameter) parameter, newParameterAnnotation);</span><br><span class="line"> parameters[i] = newParameter;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> bean;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 找到方法实现的接口方法</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@param</span> method</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> Method <span class="title">findInterfaceMethod</span><span class="params">(Method method)</span> </span>{</span><br><span class="line"> Set<Class<?>> interfaces = ClassUtils.getAllInterfacesForClassAsSet(method.getDeclaringClass());</span><br><span class="line"> <span class="keyword">for</span> (Class<?> superInterface : interfaces) {</span><br><span class="line"> Method superMethod = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> superMethod = superInterface.getMethod(method.getName(), method.getParameterTypes());</span><br><span class="line"> } <span class="keyword">catch</span> (NoSuchMethodException e) {</span><br><span class="line"> <span class="comment">//忽略异常</span></span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (superMethod != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span> superMethod;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>代码不多,注释也很清晰的描述了整个实现的逻辑,就是找到集成的接口方法,将接口方法的注解填充到<code>HandlerMethod</code>中。这里我们新建了一个<code>MethodParameter</code>的子类<code>NewHandlerMethodParameter</code>,源代码如下:<br><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><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">NewHandlerMethodParameter</span> <span class="keyword">extends</span> <span class="title">SynthesizingMethodParameter</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">volatile</span> Annotation[] parameterAnnotations;</span><br><span class="line"> <span class="keyword">private</span> Annotation[] newParameterAnnotation;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">NewHandlerMethodParameter</span><span class="params">(SynthesizingMethodParameter original, Annotation[] newParameterAnnotation)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(original);</span><br><span class="line"> <span class="keyword">this</span>.newParameterAnnotation = newParameterAnnotation;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 覆盖父类方法,返回合并接口注解的数组</span></span><br><span class="line"><span class="comment"> *</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="keyword">public</span> Annotation[] getParameterAnnotations() {</span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">this</span>.parameterAnnotations == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">this</span>.parameterAnnotations = adaptAnnotationArray(newParameterAnnotation);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>.parameterAnnotations;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里需要注意,我刚开始直继承<code>MethodParameter</code>,<code>getParameterAnnotations</code>方法直接返回<code>newParameterAnnotation</code>,是有问题的。<br>这里父类是<code>SynthesizingMethodParameter</code>,作用是使用方法<code>adaptAnnotationArray</code>将<code>newParameterAnnotation</code>进行改造,填充注解中属性<code>@AliasFor</code>的值。<br>如果没有这一步操作,会导致注解属性值不完整,比如<code>@RequestParam</code>中的<code>value</code>和<code>name</code>只填写了一个,语义上另外一个值是相同的。<br>实现原理是调用<code>AnnotationUtils.synthesizeAnnotation</code>方法,构造一个注解接口(注解实际上是接口)的新的动态代理对象,根据<code>@AliasFor</code>设置指定方法返回值。</p><p>新建spring-boot工程<code>hello-service</code>,将<code>HandlerMappingPostProcessor</code>注册为spring bean,复制<code>hello-service-problem</code>中的两个Controller</p><blockquote><p>可以参考github:<a href="https://github.com/along101/spring-boot-test/tree/master/feign-test/hello-service" target="_blank" rel="noopener">hello-service</a></p></blockquote><p>再启动spring-boot工程进在浏览器中输入url测试两个接口:</p><p><a href="http://localhost:8082/test1/hello2?name=along&age=12" target="_blank" rel="noopener">http://localhost:8082/test1/hello2?name=along&age=12</a><br>结果:<br><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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"along"</span>,</span><br><span class="line"> <span class="attr">"age"</span>: <span class="number">12</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><a href="http://localhost:8082/test2/hello2?name=along&age=12" target="_blank" rel="noopener">http://localhost:8082/test2/hello2?name=along&age=12</a><br>结果:<br><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></pre></td><td class="code"><pre><span class="line">{</span><br><span class="line"> <span class="attr">"name"</span>: <span class="string">"along"</span>,</span><br><span class="line"> <span class="attr">"age"</span>: <span class="number">12</span></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>问题解决了。</p>]]></content>
<summary type="html">
<h1 id="问题描述"><a href="#问题描述" class="headerlink" title="问题描述"></a>问题描述</h1><p>spring-cloud-openfeign 很方便的将http rest服务调用转换为基于接口的rpc调用。<br>服务端和客户端多引用相同的Rest定义接口,服务端使用RestController实现该接口,客户端通过Feign生成该接口的代理,就能保证服务端和客户端的协议。<br>但是springMvc有个缺陷,RestController继承的接口能将接口类和方法上的注解都继承过来,<strong>但是方法参数的注解却无法继承</strong>。<br>本文提供了一种解决该问题的方案,非侵入试的巧妙的解决了该问题。</p>
</summary>
<category term="spring" scheme="http://along101.site/categories/spring/"/>
<category term="spring" scheme="http://along101.site/tags/spring/"/>
<category term="rest" scheme="http://along101.site/tags/rest/"/>
</entry>
<entry>
<title>拍拍贷微服务rpc框架--raptor</title>
<link href="http://along101.site/2018/07/18/raptor-rpc/"/>
<id>http://along101.site/2018/07/18/raptor-rpc/</id>
<published>2018-07-18T19:16:13.000Z</published>
<updated>2018-07-20T03:10:04.409Z</updated>
<content type="html"><![CDATA[<h1 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h1><p>这是我在拍拍贷负责开发的微服务RPC框架,已经拍拍贷全面推广,生产稳定运行。</p><p>该RPC框架参考、借鉴了大量已有rpc框架的设计,主要特性如下:</p><ol><li>契约驱动(Contract-First)开发模式,采用protobuf契约,自动生成服务器端接口和客户端代码</li><li>基于HTTP协议,一套组件同时覆盖内部服务开发和对外开放场景</li><li>RPC/REST混合模式,既可以使用客户端以RPC/HTTP/JSON方式调用,也可以通过浏览器以REST/HTTP/JSON方式调用</li><li>支持多种强类型客户端自动生成,Java/C#/Python/iOS/Android…</li><li>设计实现简单轻量,依赖少,可以和Spring(Boot)无缝集成</li></ol><p>该框架已经开源,源码地址: <a href="https://github.com/ppdai-incubator/raptor" target="_blank" rel="noopener">https://github.com/ppdai-incubator/raptor</a></p><p>详细参考文档请参考: <a href="https://github.com/ppdai-incubator/raptor/wiki" target="_blank" rel="noopener">https://github.com/ppdai-incubator/raptor/wiki</a></p>]]></content>
<summary type="html">
<h1 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h1><p>这是我在拍拍贷负责开发的微服务RPC框架,已经拍拍贷全面推广,生产稳定运行。</p>
<p>该RPC框架参考、借鉴了大量已有rpc框架的设计
</summary>
<category term="微服务" scheme="http://along101.site/categories/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="微服务" scheme="http://along101.site/tags/%E5%BE%AE%E6%9C%8D%E5%8A%A1/"/>
<category term="rpc" scheme="http://along101.site/tags/rpc/"/>
<category term="契约驱动" scheme="http://along101.site/tags/%E5%A5%91%E7%BA%A6%E9%A9%B1%E5%8A%A8/"/>
</entry>
<entry>
<title>java 线程池原理</title>
<link href="http://along101.site/2018/07/18/java-threadpool/"/>
<id>http://along101.site/2018/07/18/java-threadpool/</id>
<published>2018-07-18T14:30:57.000Z</published>
<updated>2018-07-20T03:10:04.409Z</updated>
<content type="html"><![CDATA[<h1 id="线程生命周期及状态转换"><a href="#线程生命周期及状态转换" class="headerlink" title="线程生命周期及状态转换"></a>线程生命周期及状态转换</h1><p>我们先来看看java中线程的生命周期,已经各个状态之间的转换,用一张图能比较清晰的描述:</p><p><img src="/2018/07/18/java-threadpool/thread-status.png" alt=""></p><a id="more"></a><p>java中线程的状态一共分为6种:new、runnable、blocked、waiting、timed_waiting、terminated</p><h2 id="初始态:new"><a href="#初始态:new" class="headerlink" title="初始态:new"></a>初始态:new</h2><p>创建一个<code>Thread</code>对象,但还未调用<code>start()</code>启动线程时,线程处于初始态。</p><h2 id="运行态:runnable"><a href="#运行态:runnable" class="headerlink" title="运行态:runnable"></a>运行态:runnable</h2><p>在Java中,运行态包括<code>就绪态</code>和<code>运行态</code>。</p><ul><li>就绪态<ul><li>该状态下的线程已经获得执行所需的所有资源,只要CPU分配执行权就能运行。</li><li>所有就绪态的线程存放在就绪队列中。</li></ul></li><li>运行态<ul><li>获得CPU执行权,正在执行的线程。</li><li>由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行态的线程。</li></ul></li></ul><h2 id="阻塞态:blocked"><a href="#阻塞态:blocked" class="headerlink" title="阻塞态:blocked"></a>阻塞态:blocked</h2><ul><li>当一条正在执行的线程请求某一资源失败时,就会进入阻塞态。</li><li>而在Java中,阻塞态专指请求锁失败时进入的状态。</li><li>由一个阻塞队列存放所有阻塞态的线程。</li><li>处于阻塞态的线程会不断请求资源,一旦请求成功,就会进入就绪队列,等待执行。</li></ul><p>PS:锁、IO、Socket等都资源。</p><h2 id="等待:waiting"><a href="#等待:waiting" class="headerlink" title="等待:waiting"></a>等待:waiting</h2><ul><li>当前线程中调用wait、join、park函数时,当前线程就会进入等待态。</li><li>也有一个等待队列存放所有等待态的线程。</li><li>线程处于等待态表示它需要等待其他线程的指示才能继续运行。</li><li>进入等待态的线程会释放CPU执行权,并释放资源(如:锁)</li></ul><h2 id="超时等待:timed-waiting"><a href="#超时等待:timed-waiting" class="headerlink" title="超时等待:timed_waiting"></a>超时等待:timed_waiting</h2><ul><li>当运行中的线程调用<code>sleep(time)</code>、<code>wait(time)</code>、<code>join</code>、<code>parkNanos</code>、<code>parkUntil</code>时,就会进入该状态;</li><li>它和等待态一样,并不是因为请求不到资源,而是主动进入,并且进入后需要其他线程唤醒;</li><li>进入该状态后释放CPU执行权和占有的资源。</li><li>与等待态的区别:到了超时时间后自动进入阻塞队列,开始竞争锁。</li></ul><h2 id="终止:terminated"><a href="#终止:terminated" class="headerlink" title="终止:terminated"></a>终止:terminated</h2><p>线程执行结束后的状态。</p><h2 id="注意"><a href="#注意" class="headerlink" title="注意"></a>注意</h2><ul><li><code>wait()</code>方法会释放CPU执行权 和 占有的锁。</li><li><code>sleep(long)</code>方法仅释放CPU使用权,锁仍然占用;线程被放入超时等待队列,与<code>yield</code>相比,它会使线程较长时间得不到运行。</li><li><code>yield()</code>方法仅释放CPU执行权,锁仍然占用,线程会被放入就绪队列,会在短时间内再次执行。</li><li><code>wait</code>和<code>notify</code>必须配套使用,即必须使用同一把锁调用;</li><li><code>wait</code>和<code>notify</code>必须放在一个同步块中</li><li>调用<code>wait</code>和<code>notify</code>的对象必须是他们所处同步块的锁对象。</li></ul><h1 id="线程池的工作原理"><a href="#线程池的工作原理" class="headerlink" title="线程池的工作原理"></a>线程池的工作原理</h1><p>java线程池的核心类是<code>java.util.concurrent.ThreadPoolExecutor</code>,构造函数:<br><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><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ThreadPoolExecutor</span><span class="params">(<span class="keyword">int</span> corePoolSize, //核心线程的数量</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">int</span> maximumPoolSize, //最大线程数量</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">long</span> keepAliveTime, //超出核心线程数量以外的线程空余存活时间</span></span></span><br><span class="line"><span class="function"><span class="params"> TimeUnit unit, //存活时间的单位</span></span></span><br><span class="line"><span class="function"><span class="params"> BlockingQueue<Runnable> workQueue, //保存待执行任务的队列</span></span></span><br><span class="line"><span class="function"><span class="params"> ThreadFactory threadFactory, //创建新线程使用的工厂</span></span></span><br><span class="line"><span class="function"><span class="params"> RejectedExecutionHandler handler // 当任务无法执行时的处理器</span></span></span><br><span class="line"><span class="function"><span class="params"> )</span> </span>{</span><br><span class="line"> 。。。</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>参数介绍如注释所示,要了解这些参数左右着什么,就需要了解线程池具体的执行方法<code>ThreadPoolExecutor.execute</code>:<br><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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">execute</span><span class="params">(Runnable command)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (command == <span class="keyword">null</span>)</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException();</span><br><span class="line"> <span class="keyword">int</span> c = ctl.get();</span><br><span class="line"> <span class="comment">//1.当前池中线程比核心数少,新建一个线程执行任务</span></span><br><span class="line"> <span class="keyword">if</span> (workerCountOf(c) < corePoolSize) {</span><br><span class="line"> <span class="keyword">if</span> (addWorker(command, <span class="keyword">true</span>))</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> c = ctl.get();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//2.核心池已满,但任务队列未满,添加到队列中</span></span><br><span class="line"> <span class="keyword">if</span> (isRunning(c) && workQueue.offer(command)) {</span><br><span class="line"> <span class="keyword">int</span> recheck = ctl.get();</span><br><span class="line"> <span class="comment">////如果这时被关闭了,拒绝任务</span></span><br><span class="line"> <span class="keyword">if</span> (! isRunning(recheck) && remove(command))</span><br><span class="line"> reject(command);</span><br><span class="line"> <span class="comment">//如果之前的线程已被销毁完,新建一个线程</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (workerCountOf(recheck) == <span class="number">0</span>)</span><br><span class="line"> addWorker(<span class="keyword">null</span>, <span class="keyword">false</span>);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//3.核心池已满,队列已满,试着创建一个新线程</span></span><br><span class="line"> <span class="keyword">else</span> <span class="keyword">if</span> (!addWorker(command, <span class="keyword">false</span>))</span><br><span class="line"> reject(command);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>可以看到,线程池处理一个任务主要分三步处理,代码注释里已经介绍了,我再用通俗易懂的例子解释一下:</p><p>(线程比作员工,<code>ThreadPoolExecutor</code>比作团队领导,核心池比作团队中编制员工数,核心池外的比作外包员工)</p><ol><li><p>有了新需求,先看有没有空闲的编制,有的话就招一个编制人员来做</p><blockquote><p>有点奇怪,为啥不利用现有空闲的编制员工?领导可能觉得先把人招满,有资源不用白不用</p></blockquote></li><li><p>如果编制人员已经满了,领导就把任务放到任务列表里面,编制人员自己去取任务干活</p><blockquote><p>编制员工都很自觉,盯着领导手上的任务,干完手头上的活立马去抢,都是好员工</p></blockquote></li><li><p>如果任务列表满了,活太多编制员工干不完了,领导就会去招外包来干</p><blockquote><p>虽然是外包,也很主动,干完手头上的活也去抢领导的手上的任务,悲哀的是,领导手上没有任务了,外包就自己走了,真是良心外包<br>我做外包这么多年,虽然很主动,但是赖着不走,多算人员多要钱</p></blockquote></li><li><p>外包也有限制,外包都到限制了,那没有办法,就要拒绝需求了</p><blockquote><p>需求不是你想拒绝就拒绝的,产品经理不同意怎么办?开会协商定策略吧。</p></blockquote></li></ol><p>结合这张图,应该能明白:</p><p><img src="/2018/07/18/java-threadpool/thread-pool.png" alt=""></p><p>前面提到的<code>ThreadPoolExecutor</code>构造函数的参数,分别影响以下内容:</p><ul><li><p><code>int corePoolSize</code>:核心线程池数量 </p><ul><li>在线程数少于核心数量时,有新任务进来就新建一个线程,即使有的线程没事干</li><li>等超出核心数量后,就不会新建线程了,空闲的线程就得去任务队列里取任务执行了<blockquote><p>编制员工</p></blockquote></li></ul></li><li><p><code>int maximumPoolSize</code>:最大线程数量 </p><ul><li>包括核心线程池数量 + 核心以外的数量</li><li>如果任务队列满了,并且池中线程数小于最大线程数,会再创建新的线程执行任务<blockquote><p>外包员工</p></blockquote></li></ul></li><li><p><code>long keepAliveTime</code>:核心池以外的线程存活时间,即没有任务的外包的存活时间 </p><ul><li>如果给线程池设置<code>allowCoreThreadTimeOut(true)</code>,则核心线程在空闲时头上也会响起死亡的倒计时</li><li>如果任务是多而容易执行的,可以调大这个参数,那样线程就可以在存活的时间里有更大可能接受新任务<blockquote><p>外包员工比较惨,没事干了,就得滚蛋,卸磨杀驴啊<br>编制员工好多了,不过领导心情不好,没事干了,核心员工也得滚蛋</p></blockquote></li></ul></li><li><p><code>BlockingQueue<Runnable> workQueue</code>:保存待执行任务的阻塞队列 </p><ul><li>不同的任务类型有不同的选择,下一小节介绍<blockquote><p>领导手上的任务表</p></blockquote></li></ul></li><li><p><code>ThreadFactory threadFactory</code>:每个线程创建的地方 </p><ul><li>可以给线程起个好听的名字,设置个优先级啥的</li></ul></li><li><p><code>RejectedExecutionHandler handler</code>:饱和策略,大家都很忙,咋办呢,有四种策略 </p><ul><li><code>CallerRunsPolicy</code>:只要线程池没关闭,直接用调用者所在线程来运行任务,就是你自己干</li><li><code>AbortPolicy</code>:直接抛出<code>RejectedExecutionException</code>异常</li><li><code>DiscardPolicy</code>:悄悄把任务放生,不做了</li><li><code>DiscardOldestPolicy</code>:把队列里待最久的那个任务扔了,然后再调用<code>execute()</code>试试看能行不</li><li>我们也可以实现自己的<code>RejectedExecutionHandler</code>接口自定义策略,比如调用<code>workQueue.put</code>方法阻塞住,需求方一直等着,只要团队有空就提需求</li></ul></li></ul><h2 id="阻塞队列-workQueue"><a href="#阻塞队列-workQueue" class="headerlink" title="阻塞队列 workQueue"></a>阻塞队列 workQueue</h2><p>当线程池中的核心线程数已满时,任务就要保存到队列中了。</p><p>线程池中使用的队列是<code>BlockingQueue</code>接口,常用的实现有如下几种:</p><ul><li><p><code>ArrayBlockingQueue</code>:基于数组、有界,按FIFO(先进先出)原则对元素进行排序</p></li><li><p><code>LinkedBlockingQueue</code>:基于链表,按FIFO(先进先出) 排序元素 </p><ul><li>吞吐量通常要高于<code>ArrayBlockingQueue</code></li><li><code>Executors.newFixedThreadPool()</code> 使用了这个队列</li></ul></li><li><p><code>SynchronousQueue</code>:不存储元素的阻塞队列 </p><ul><li>每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态</li><li>吞吐量通常要高于<code>LinkedBlockingQueue</code></li><li><code>Executors.newCachedThreadPool</code>使用了这个队列</li></ul></li><li><p><code>PriorityBlockingQueue</code>:具有优先级的、无限阻塞队列</p></li></ul><h2 id="Executors-工厂创建线程池"><a href="#Executors-工厂创建线程池" class="headerlink" title="Executors 工厂创建线程池"></a>Executors 工厂创建线程池</h2><h3 id="newFixedThreadPool"><a href="#newFixedThreadPool" class="headerlink" title="newFixedThreadPool"></a>newFixedThreadPool</h3><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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newFixedThreadPool</span><span class="params">(<span class="keyword">int</span> nThreads, ThreadFactory threadFactory)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ThreadPoolExecutor(nThreads, nThreads,</span><br><span class="line"> <span class="number">0L</span>, TimeUnit.MILLISECONDS,</span><br><span class="line"> <span class="keyword">new</span> LinkedBlockingQueue<Runnable>(),</span><br><span class="line"> threadFactory);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>不招外包,有固定数量核心成员的正常互联网团队。</p><p>可以看到,<code>FixedThreadPool</code>的核心线程数和最大线程数都是指定值,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。</p><p>而这里选用的阻塞队列是<code>LinkedBlockingQueue</code>,使用的是默认容量<code>Integer.MAX_VALUE</code>,相当于没有上限。</p><blockquote><p>这个坑比较大,千万要注意,阿里巴巴java开发手册明确提醒,不能直接使用Executors</p></blockquote><h3 id="newSingleThreadExecutor"><a href="#newSingleThreadExecutor" class="headerlink" title="newSingleThreadExecutor"></a>newSingleThreadExecutor</h3><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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newSingleThreadExecutor</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> FinalizableDelegatedExecutorService</span><br><span class="line"> (<span class="keyword">new</span> ThreadPoolExecutor(<span class="number">1</span>, <span class="number">1</span>,</span><br><span class="line"> <span class="number">0L</span>, TimeUnit.MILLISECONDS,</span><br><span class="line"> <span class="keyword">new</span> LinkedBlockingQueue<Runnable>()));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>不招外包,只有一个核心成员的创业团队。</p><p>从参数可以看出来,<code>SingleThreadExecutor</code>相当于参数为1的<code>FixedThreadPool</code>。</p><h3 id="newCachedThreadPool"><a href="#newCachedThreadPool" class="headerlink" title="newCachedThreadPool"></a>newCachedThreadPool</h3><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ExecutorService <span class="title">newCachedThreadPool</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ThreadPoolExecutor(<span class="number">0</span>, Integer.MAX_VALUE,</span><br><span class="line"> <span class="number">60L</span>, TimeUnit.SECONDS,</span><br><span class="line"> <span class="keyword">new</span> SynchronousQueue<Runnable>());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>全部外包,没活最多待60秒的外包团队。</p><p>可以看到,<code>CachedThreadPool</code>没有核心线程,非核心线程数无上限,也就是全部使用外包,但是每个外包空闲的时间只有60秒,超过后就会被回收。</p><p><code>CachedThreadPool</code>使用的队列是<code>SynchronousQueue</code>,这个队列的作用就是传递任务,并不会保存。</p><p>因此当提交任务的速度大于处理任务的速度时,每次提交一个任务,就会创建一个线程。极端情况下会创建过多的线程,耗尽CPU和内存资源。</p><h3 id="newScheduledThreadPool"><a href="#newScheduledThreadPool" class="headerlink" title="newScheduledThreadPool"></a>newScheduledThreadPool</h3><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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> ScheduledExecutorService <span class="title">newScheduledThreadPool</span><span class="params">(<span class="keyword">int</span> corePoolSize)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ScheduledThreadPoolExecutor(corePoolSize);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">ScheduledThreadPoolExecutor</span><span class="params">(<span class="keyword">int</span> corePoolSize)</span> </span>{</span><br><span class="line"> <span class="keyword">super</span>(corePoolSize, Integer.MAX_VALUE, <span class="number">0</span>, NANOSECONDS,</span><br><span class="line"> <span class="keyword">new</span> DelayedWorkQueue());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>定期维护的 2B 业务团队,核心与外包成员都有。</p><p><code>ScheduledThreadPoolExecutor</code>继承自<code>ThreadPoolExecutor</code>, 最多线程数为<code>Integer.MAX_VALUE</code>,使用<code>DelayedWorkQueue</code>作为任务队列。</p><p><code>ScheduledThreadPoolExecutor</code>添加任务和执行任务的机制与<code>ThreadPoolExecutor</code>有所不同。</p><p><code>ScheduledThreadPoolExecutor</code>添加任务提供了另外两个方法:</p><ul><li><code>scheduleAtFixedRate()</code>:按某种速率周期执行</li><li><code>scheduleWithFixedDelay()</code>:在某个延迟后执行</li></ul><h2 id="提交任务的方法"><a href="#提交任务的方法" class="headerlink" title="提交任务的方法"></a>提交任务的方法</h2><p><code>ExecutorService</code>提供了两种提交任务的方法:</p><ol><li><code>execute()</code>:提交不需要返回值的任务</li><li><code>submit()</code>:提交需要返回值的任务</li></ol><h2 id="关闭线程池"><a href="#关闭线程池" class="headerlink" title="关闭线程池"></a>关闭线程池</h2><p>线程池即使不执行任务也会占用一些资源,所以在我们要退出任务时最好关闭线程池。</p><p>有两个方法关闭线程池:</p><ol><li><p><code>shutdown()</code></p><ul><li>将线程池的状态设置为 SHUTDOWN,并不会立即停止</li><li>停止接收外部submit的任务</li><li>内部正在跑的任务和队列里等待的任务,会执行完</li><li>等到上一步完成后,才真正停止</li></ul></li><li><p><code>shutdownNow()</code></p><ul><li>将线程池设置为STOP,企图立即停止</li><li>跟<code>shutdown()</code>一样,先停止接收外部提交的任务</li><li>忽略队列里等待的任务</li><li>尝试将正在跑的任务<code>interrupt</code>中断</li><li>返回未执行的任务列表</li></ul></li></ol><h2 id="等待线程结束"><a href="#等待线程结束" class="headerlink" title="等待线程结束"></a>等待线程结束</h2><p><code>awaitTermination</code>当前线程阻塞,直到</p><pre><code>- 等所有已提交的任务(包括正在跑的和队列中等待的)执行完- 或者等超时时间到- 或者线程被中断,抛出`InterruptedException`</code></pre><blockquote><p>优雅的关闭线程池,一般用<code>shutdown()</code>+<code>awaitTermination()</code></p></blockquote><h1 id="源代码分析"><a href="#源代码分析" class="headerlink" title="源代码分析"></a>源代码分析</h1><p>前面已经描述的很清楚了,再去看源代码就很容易,不在这里写了。</p>]]></content>
<summary type="html">
<h1 id="线程生命周期及状态转换"><a href="#线程生命周期及状态转换" class="headerlink" title="线程生命周期及状态转换"></a>线程生命周期及状态转换</h1><p>我们先来看看java中线程的生命周期,已经各个状态之间的转换,用一张图能比较清晰的描述:</p>
<p><img src="/2018/07/18/java-threadpool/thread-status.png" alt=""></p>
</summary>
<category term="java" scheme="http://along101.site/categories/java/"/>
<category term="java" scheme="http://along101.site/tags/java/"/>
<category term="thread" scheme="http://along101.site/tags/thread/"/>
</entry>
<entry>
<title>java8 reactor模式的实现之CompletableFuture</title>
<link href="http://along101.site/2018/07/17/java8-CompleteableFuture/"/>
<id>http://along101.site/2018/07/17/java8-CompleteableFuture/</id>
<published>2018-07-17T17:11:57.000Z</published>
<updated>2018-07-20T03:10:04.409Z</updated>
<content type="html"><![CDATA[<h1 id="CompletableFuture简介"><a href="#CompletableFuture简介" class="headerlink" title="CompletableFuture简介"></a>CompletableFuture简介</h1><p>java8中新增加了java.util.concurrent.CompletableFuture类,作用类似RxJava。</p><p>实际开发中,我们经常需要达成以下目的:</p><ul><li>将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。</li><li>等待 Future 集合中的所有任务都完成。</li><li>仅等待 Future 集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。</li><li>通过编程方式完成一个 Future 任务的执行(即以手工设定异步操作结果的方式)。</li><li>应对 Future 的完成事件(即当 Future 的完成事件发生时会收到通知,并能使用 Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)</li></ul><p>新的CompletableFuture将使得这些成为可能。</p><a id="more"></a><h2 id="工厂构建"><a href="#工厂构建" class="headerlink" title="工厂构建"></a>工厂构建</h2><ul><li><p>runAsync , supplyAsync</p><blockquote><p>静态方法,创建一个CompletableFuture,runAsync没有返回值,supplyAsync有返回值</p></blockquote></li><li><p>completedFuture</p><blockquote><p>静态方法,创建一个已经完成的CompletableFuture</p></blockquote></li></ul><h2 id="等待完成"><a href="#等待完成" class="headerlink" title="等待完成"></a>等待完成</h2><ul><li><p>get</p><blockquote><p>等待完成,并获取结果</p></blockquote></li><li><p>join</p><blockquote><p>将异步执行的线程join过来,跟get类似,等待完成,但是是不返回结果</p></blockquote></li></ul><h2 id="设置状态"><a href="#设置状态" class="headerlink" title="设置状态"></a>设置状态</h2><ul><li><p>complete,completeExceptionally</p><blockquote><p>设置CompletableFuture为完成,用指定的对象返回</p></blockquote></li><li><p>obtrudeValue,obtrudeException</p><blockquote><p>强制返回/抛异常</p></blockquote></li><li><p>cancel</p><blockquote><p>取消</p></blockquote></li></ul><h2 id="根据状态操作"><a href="#根据状态操作" class="headerlink" title="根据状态操作"></a>根据状态操作</h2><ul><li><p>whenComplete</p><blockquote><p>当本 CompletableFuture 执行完成后使用 BiConsumer 函数返回一个新的CompletableFuture, BiConsumer 第一个参数为返回值,第二个参数为异常</p></blockquote></li><li><p>handle</p><blockquote><p>当本CompletableFuture 执行完成后执行 BiConsumer 的函数,BiFunction第一个参数为返回值,第二个参数为异常,需要返回</p></blockquote></li><li><p>exceptionally</p><blockquote><p>当本CompletableFuture执行异常时,使用Function返回一个新的CompletableFuture</p></blockquote></li></ul><h2 id="状态判断"><a href="#状态判断" class="headerlink" title="状态判断"></a>状态判断</h2><ul><li>isDone,isCompletedExceptionally,isCancelled<blockquote><p>返回是否完成/异常/取消</p></blockquote></li></ul><h2 id="逻辑转换,单个"><a href="#逻辑转换,单个" class="headerlink" title="逻辑转换,单个"></a>逻辑转换,单个</h2><ul><li><p>thenApply,thenApplyAsync</p><blockquote><p>CompletableFuture正常结束后,使用Function函数应用一个新的CompletableFuture,Function函数有返回值</p></blockquote></li><li><p>thenAccept</p><blockquote><p>CompletableFuture正常结束后,使用Consumer函数应用一个新的CompletableFuture,Consumer函数没有返回值</p></blockquote></li><li><p>thenRun</p><blockquote><p>CompletableFuture正常结束后,使用Runnable函数应用一个新的CompletableFuture,Runnable函数没有输入值和返回值</p></blockquote></li></ul><h1 id="逻辑转换,两个"><a href="#逻辑转换,两个" class="headerlink" title="逻辑转换,两个"></a>逻辑转换,两个</h1><ul><li><p>thenAcceptBoth</p><blockquote><p>当本CompletableFuture和给定的CompletableFuture正常完成后,执行BiFunction函数并返回,BiFunction的输入参数为两个CompletableFuture的返回值</p></blockquote></li><li><p>thenCombine</p><blockquote><p>当本CompletableFuture和给定的CompletableFuture正常完成后,使用BiFunction函数生成一个新的CompletableFuture,BiFunction的输入参数为两个CompletableFuture的返回值。<br>两个CompletableFuture执行完后再执行指定函数</p></blockquote></li><li><p>thenCompose</p><blockquote><p>当本CompletableFuture完成后,将返回结果作为参数传递给Function函数,此Function函数返回一个新的CompletableFuture。<br>第一个CompletableFuture执行完后,结果传递给第二个CompletableFuture,返回第二个CompletableFuture</p></blockquote></li><li><p>applyToEither</p><blockquote><p>本 CompletableFuture 或者给定的CompletableFuture,任意一个做完后,使用Function生成一个新的CompletableFuture并返回</p></blockquote></li><li><p>acceptEither</p><blockquote><p>本 CompletableFuture 或者给定的CompletableFuture,任意一个做完后,使用Consumer生成一个新的CompletableFuture并返回</p></blockquote></li><li><p>runAfterEither</p><blockquote><p>本 CompletableFuture 或者给定的 CompletableFuture,任意一个做完后,使用Runnable生成一个新的CompletableFuture并返回</p></blockquote></li><li><p>runAfterBoth</p><blockquote><p>本CompletableFuture和给定的CompletableFuture,都完成后,使用Runnable生成一个新的CompletableFuture并返回</p></blockquote></li></ul><h2 id="逻辑转换,多个"><a href="#逻辑转换,多个" class="headerlink" title="逻辑转换,多个"></a>逻辑转换,多个</h2><ul><li><p>allOf</p><blockquote><p>静态方法,返回一个新的CompletableFuture,当所有的CompletableFuture都完成后才完成</p></blockquote></li><li><p>anyOf</p><blockquote><p>静态方法,返回一个新的CompletableFuture,任意一个CompletableFuture都完成后就完成</p></blockquote></li></ul><h1 id="demo"><a href="#demo" class="headerlink" title="demo"></a>demo</h1><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><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><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br><span class="line">167</span><br><span class="line">168</span><br><span class="line">169</span><br><span class="line">170</span><br><span class="line">171</span><br><span class="line">172</span><br><span class="line">173</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">CompletableFutureTest</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">sleep</span><span class="params">(<span class="keyword">int</span> s)</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> TimeUnit.SECONDS.sleep(s);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test1</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> CompletableFuture<String> completableFuture = <span class="keyword">new</span> CompletableFuture<>();</span><br><span class="line"> <span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//告诉completableFuture任务已经完成</span></span><br><span class="line"> completableFuture.complete(<span class="string">"result"</span>);</span><br><span class="line"> }).start();</span><br><span class="line"> <span class="comment">//获取任务结果,如果没有完成会一直阻塞等待</span></span><br><span class="line"> String result = completableFuture.get();</span><br><span class="line"> System.out.println(<span class="string">"计算结果:"</span> + result);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test2</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> CompletableFuture<String> completableFuture = <span class="keyword">new</span> CompletableFuture<>();</span><br><span class="line"> <span class="keyword">new</span> Thread(() -> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> RuntimeException(<span class="string">"抛异常了"</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> <span class="comment">//告诉completableFuture任务发生异常了</span></span><br><span class="line"> completableFuture.completeExceptionally(e);</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> <span class="comment">//获取任务结果,如果没有完成会一直阻塞等待</span></span><br><span class="line"> String result = completableFuture.get();</span><br><span class="line"> System.out.println(<span class="string">"计算结果:"</span> + result);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test3</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">//supplyAsync内部使用ForkJoinPool线程池执行任务</span></span><br><span class="line"> CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"result"</span>;</span><br><span class="line"> });</span><br><span class="line"> System.out.println(<span class="string">"计算结果:"</span> + completableFuture.get());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test4</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task1 doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"result1"</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> CompletableFuture<String> completableFuture2 = CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task2 doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"result2"</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> CompletableFuture<Object> anyResult = CompletableFuture.anyOf(completableFuture1, completableFuture2);</span><br><span class="line"> System.out.println(<span class="string">"第一个完成的任务结果:"</span> + anyResult.get());</span><br><span class="line"> CompletableFuture<Void> allResult = CompletableFuture.allOf(completableFuture1, completableFuture2);</span><br><span class="line"> <span class="comment">//阻塞等待所有任务执行完成</span></span><br><span class="line"> allResult.join();</span><br><span class="line"> System.out.println(<span class="string">"所有任务执行完成"</span>);</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test5</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> CompletableFuture<String> completableFuture1 = CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task1 doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"result1"</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">//等第一个任务完成后,将任务结果传给参数result,执行后面的任务并返回一个代表任务的completableFuture</span></span><br><span class="line"> CompletableFuture<String> completableFuture2 = completableFuture1.thenCompose(result -> CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task2 doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="string">"result2"</span>;</span><br><span class="line"> }));</span><br><span class="line"></span><br><span class="line"> System.out.println(completableFuture2.get());</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test6</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"></span><br><span class="line"> CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task1 doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">100</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">//将第一个任务与第二个任务组合一起执行,都执行完成后,将两个任务的结果合并</span></span><br><span class="line"> CompletableFuture<Integer> completableFuture2 = completableFuture1.thenCombine(</span><br><span class="line"> <span class="comment">//第二个任务</span></span><br><span class="line"> CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task2 doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">2000</span>;</span><br><span class="line"> }),</span><br><span class="line"> <span class="comment">//合并函数</span></span><br><span class="line"> (result1, result2) -> result1 + result2);</span><br><span class="line"></span><br><span class="line"> System.out.println(completableFuture2.get());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test7</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"></span><br><span class="line"> CompletableFuture<Integer> completableFuture1 = CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task1 doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">100</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">//注册完成事件</span></span><br><span class="line"> completableFuture1.thenAccept(result -> System.out.println(<span class="string">"task1 done,result:"</span> + result));</span><br><span class="line"> <span class="comment">//第二个任务</span></span><br><span class="line"> CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(() -> {</span><br><span class="line"> <span class="comment">//模拟执行耗时任务</span></span><br><span class="line"> System.out.println(<span class="string">"task2 doing..."</span>);</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="comment">//返回结果</span></span><br><span class="line"> <span class="keyword">return</span> <span class="number">2000</span>;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> <span class="comment">//注册完成事件</span></span><br><span class="line"> completableFuture2.thenAccept(result -> System.out.println(<span class="string">"task2 done,result:"</span> + result));</span><br><span class="line"></span><br><span class="line"> <span class="comment">//将第一个任务与第二个任务组合一起执行,都执行完成后,将两个任务的结果合并</span></span><br><span class="line"> CompletableFuture<Integer> completableFuture3 = completableFuture1.thenCombine(completableFuture2,</span><br><span class="line"> <span class="comment">//合并函数</span></span><br><span class="line"> (result1, result2) -> {</span><br><span class="line"> sleep(<span class="number">1</span>);</span><br><span class="line"> <span class="keyword">return</span> result1 + result2;</span><br><span class="line"> });</span><br><span class="line"></span><br><span class="line"> System.out.println(completableFuture3.get());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure>]]></content>
<summary type="html">
<h1 id="CompletableFuture简介"><a href="#CompletableFuture简介" class="headerlink" title="CompletableFuture简介"></a>CompletableFuture简介</h1><p>java8中新增加了java.util.concurrent.CompletableFuture类,作用类似RxJava。</p>
<p>实际开发中,我们经常需要达成以下目的:</p>
<ul>
<li>将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。</li>
<li>等待 Future 集合中的所有任务都完成。</li>
<li>仅等待 Future 集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。</li>
<li>通过编程方式完成一个 Future 任务的执行(即以手工设定异步操作结果的方式)。</li>
<li>应对 Future 的完成事件(即当 Future 的完成事件发生时会收到通知,并能使用 Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)</li>
</ul>
<p>新的CompletableFuture将使得这些成为可能。</p>
</summary>
<category term="java" scheme="http://along101.site/categories/java/"/>
<category term="java8" scheme="http://along101.site/tags/java8/"/>
<category term="reactor" scheme="http://along101.site/tags/reactor/"/>
<category term="CompletableFuture" scheme="http://along101.site/tags/CompletableFuture/"/>
</entry>
<entry>
<title>java8 stream应用</title>
<link href="http://along101.site/2018/07/17/java8-stream/"/>
<id>http://along101.site/2018/07/17/java8-stream/</id>
<published>2018-07-17T17:05:57.000Z</published>
<updated>2018-07-20T03:10:04.409Z</updated>
<content type="html"><![CDATA[<h1 id="stream简介"><a href="#stream简介" class="headerlink" title="stream简介"></a>stream简介</h1><p>Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。</p><a id="more"></a><h1 id="stream的生成"><a href="#stream的生成" class="headerlink" title="stream的生成"></a>stream的生成</h1><h3 id="从-Collection-和数组"><a href="#从-Collection-和数组" class="headerlink" title="从 Collection 和数组"></a>从 Collection 和数组</h3><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><span class="line">Collection.stream()</span><br><span class="line">Collection.parallelStream()</span><br><span class="line">Arrays.stream(T array) </span><br><span class="line">Stream.of()</span><br></pre></td></tr></table></figure><h3 id="从-BufferedReader"><a href="#从-BufferedReader" class="headerlink" title="从 BufferedReader"></a>从 BufferedReader</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">java.io.BufferedReader.lines()</span><br></pre></td></tr></table></figure><h3 id="静态工厂"><a href="#静态工厂" class="headerlink" title="静态工厂"></a>静态工厂</h3><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">java.util.stream.IntStream.range()</span><br><span class="line">java.nio.file.Files.walk()</span><br></pre></td></tr></table></figure><h3 id="其它"><a href="#其它" class="headerlink" title="其它"></a>其它</h3><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><span class="line">Random.ints()</span><br><span class="line">BitSet.stream()</span><br><span class="line">Pattern.splitAsStream(java.lang.CharSequence)</span><br><span class="line">JarFile.stream()</span><br></pre></td></tr></table></figure><h3 id="Stream-generate"><a href="#Stream-generate" class="headerlink" title="Stream.generate"></a>Stream.generate</h3><ul><li>生成 10 个随机整数<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><span class="line">Random seed = <span class="keyword">new</span> Random();</span><br><span class="line">Supplier<Integer> random = seed::nextInt;</span><br><span class="line">Stream.generate(random).limit(<span class="number">10</span>).forEach(System.out::println);</span><br><span class="line"><span class="comment">//Another way</span></span><br><span class="line">IntStream.generate(() -> (<span class="keyword">int</span>) (System.nanoTime() % <span class="number">100</span>)).</span><br><span class="line">limit(<span class="number">10</span>).forEach(System.out::println);</span><br></pre></td></tr></table></figure></li></ul><h3 id="Stream-iterate"><a href="#Stream-iterate" class="headerlink" title="Stream.iterate"></a>Stream.iterate</h3><p>iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。</p><ul><li>生成一个等差数列<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Stream.iterate(<span class="number">0</span>, n -> n + <span class="number">3</span>).limit(<span class="number">10</span>). forEach(x -> System.out.print(x + <span class="string">" "</span>));</span><br></pre></td></tr></table></figure></li></ul><h1 id="流的使用详解"><a href="#流的使用详解" class="headerlink" title="流的使用详解"></a>流的使用详解</h1><blockquote><p>简单说,对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用</p></blockquote><p><strong>当把一个数据结构包装成Stream 后,就要开始对里面的元素进行各类操作了。常见的操作可以归类如下。</strong></p><ul><li>Intermediate:</li></ul><blockquote><p>map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered</p></blockquote><ul><li>Terminal:</li></ul><blockquote><p>forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator</p></blockquote><ul><li>Short-circuiting:</li></ul><blockquote><p>anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit</p></blockquote><h3 id="map-flatMap"><a href="#map-flatMap" class="headerlink" title="map/flatMap"></a>map/flatMap</h3><p>作用就是把 input Stream 的每一个元素,映射成 output Stream 的另外一个元素。</p><ul><li><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></pre></td><td class="code"><pre><span class="line">List<String> output = wordList.stream().</span><br><span class="line">map(String::toUpperCase).</span><br><span class="line">collect(Collectors.toList());</span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line">List<Integer> nums = Arrays.asList(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>);</span><br><span class="line">List<Integer> squareNums = nums.stream().</span><br><span class="line">map(n -> n * n).</span><br><span class="line">collect(Collectors.toList());</span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line">Stream<List<Integer>> inputStream = Stream.of(</span><br><span class="line"> Arrays.asList(<span class="number">1</span>),</span><br><span class="line"> Arrays.asList(<span class="number">2</span>, <span class="number">3</span>),</span><br><span class="line"> Arrays.asList(<span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>)</span><br><span class="line"> );</span><br><span class="line">Stream<Integer> outputStream = inputStream.</span><br><span class="line">flatMap((childList) -> childList.stream());</span><br></pre></td></tr></table></figure></li></ul><h3 id="filter"><a href="#filter" class="headerlink" title="filter"></a>filter</h3><p>filter 对原始 Stream 进行某项测试,通过测试的元素被留下来生成一个新 Stream。</p><ul><li><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></pre></td><td class="code"><pre><span class="line">Integer[] sixNums = {<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>};</span><br><span class="line">Integer[] evens =</span><br><span class="line">Stream.of(sixNums).filter(n -> n%<span class="number">2</span> == <span class="number">0</span>).toArray(Integer[]::<span class="keyword">new</span>);</span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line">List<String> output = reader.lines().</span><br><span class="line"> flatMap(line -> Stream.of(line.split(REGEXP))).</span><br><span class="line"> filter(word -> word.length() > <span class="number">0</span>).</span><br><span class="line"> collect(Collectors.toList());</span><br></pre></td></tr></table></figure></li></ul><h3 id="forEach"><a href="#forEach" class="headerlink" title="forEach"></a>forEach</h3><p>forEach 方法接收一个 Lambda 表达式,然后在 Stream 的每一个元素上执行该表达式。</p><ul><li>打印姓名(forEach 和 pre-java8 的对比)<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><span class="line"><span class="comment">// Java 8</span></span><br><span class="line">roster.stream()</span><br><span class="line"> .filter(p -> p.getGender() == Person.Sex.MALE)</span><br><span class="line"> .forEach(p -> System.out.println(p.getName()));</span><br><span class="line"><span class="comment">// Pre-Java 8</span></span><br><span class="line"><span class="keyword">for</span> (Person p : roster) {</span><br><span class="line"> <span class="keyword">if</span> (p.getGender() == Person.Sex.MALE) {</span><br><span class="line"> System.out.println(p.getName());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h3 id="findFirst"><a href="#findFirst" class="headerlink" title="findFirst"></a>findFirst</h3><p>这是一个 termimal 兼 short-circuiting 操作,它总是返回 Stream 的第一个元素,或者空。这里比较重点的是它的返回值类型:Optional。这也是一个模仿 Scala 语言中的概念,作为一个容器,它可能含有某值,或者不包含。使用它的目的是尽可能避免 NullPointerException。</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><span class="line">String strA = <span class="string">" abcd "</span>, strB = <span class="keyword">null</span>;</span><br><span class="line">print(strA);</span><br><span class="line">print(<span class="string">""</span>);</span><br><span class="line">print(strB);</span><br><span class="line">getLength(strA);</span><br><span class="line">getLength(<span class="string">""</span>);</span><br><span class="line">getLength(strB);</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">print</span><span class="params">(String text)</span> </span>{</span><br><span class="line"> <span class="comment">// Java 8</span></span><br><span class="line"> Optional.ofNullable(text).ifPresent(System.out::println);</span><br><span class="line"> <span class="comment">// Pre-Java 8</span></span><br><span class="line"> <span class="keyword">if</span> (text != <span class="keyword">null</span>) {</span><br><span class="line"> System.out.println(text);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">int</span> <span class="title">getLength</span><span class="params">(String text)</span> </span>{</span><br><span class="line"> <span class="comment">// Java 8</span></span><br><span class="line"><span class="keyword">return</span> Optional.ofNullable(text).map(String::length).orElse(-<span class="number">1</span>);</span><br><span class="line"> <span class="comment">// Pre-Java 8</span></span><br><span class="line"><span class="comment">// return if (text != null) ? text.length() : -1;</span></span><br><span class="line"> };</span><br></pre></td></tr></table></figure><blockquote><p>在更复杂的 if (xx != null) 的情况中,使用 Optional 代码的可读性更好,而且它提供的是编译时检查,能极大的降低 NPE 这种 Runtime Exception 对程序的影响,或者迫使程序员更早的在编码阶段处理空值问题,而不是留到运行时再发现和调试。Stream 中的 findAny、max/min、reduce 等方法等返回 Optional 值。还有例如 IntStream.average() 返回 OptionalDouble 等等。</p></blockquote><h3 id="reduce"><a href="#reduce" class="headerlink" title="reduce"></a>reduce</h3><blockquote><p>T reduce(T identity, BinaryOperator<t> accumulator) 这个方法的主要作用是把 Stream 元素组合起来。它提供一个起始值(种子),然后依照运算规则(BinaryOperator),和前面 Stream 的第一个、第二个、第 n 个元素组合。从这个意义上说,字符串拼接、数值的 sum、min、max、average 都是特殊的 reduce.也有没有起始值的情况,这时会把 Stream 的前面两个元素组合起来,返回的是 Optional。</t></p></blockquote><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><span class="line"><span class="comment">// 字符串连接,concat = "ABCD"</span></span><br><span class="line">String concat = Stream.of(<span class="string">"A"</span>, <span class="string">"B"</span>, <span class="string">"C"</span>, <span class="string">"D"</span>).reduce(<span class="string">""</span>, String::concat); </span><br><span class="line"><span class="comment">// 求最小值,minValue = -3.0</span></span><br><span class="line"><span class="keyword">double</span> minValue = Stream.of(-<span class="number">1.5</span>, <span class="number">1.0</span>, -<span class="number">3.0</span>, -<span class="number">2.0</span>).reduce(Double.MAX_VALUE, Double::min); </span><br><span class="line"><span class="comment">// 求和,sumValue = 10, 有起始值</span></span><br><span class="line"><span class="keyword">int</span> sumValue = Stream.of(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>).reduce(<span class="number">0</span>, Integer::sum);</span><br><span class="line"><span class="comment">// 求和,sumValue = 10, 无起始值</span></span><br><span class="line">sumValue = Stream.of(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>).reduce(Integer::sum).get();</span><br><span class="line"><span class="comment">// 过滤,字符串连接,concat = "ace"</span></span><br><span class="line">concat = Stream.of(<span class="string">"a"</span>, <span class="string">"B"</span>, <span class="string">"c"</span>, <span class="string">"D"</span>, <span class="string">"e"</span>, <span class="string">"F"</span>).</span><br><span class="line"> filter(x -> x.compareTo(<span class="string">"Z"</span>) > <span class="number">0</span>).</span><br><span class="line"> reduce(<span class="string">""</span>, String::concat);</span><br></pre></td></tr></table></figure><h3 id="limit-skip"><a href="#limit-skip" class="headerlink" title="limit/skip"></a>limit/skip</h3><blockquote><p>limit 返回 Stream 的前面 n 个元素;skip 则是扔掉前 n 个元素(它是由一个叫 subStream 的方法改名而来)。</p></blockquote><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testLimitAndSkip</span><span class="params">()</span> </span>{</span><br><span class="line"> List<Person> persons = <span class="keyword">new</span> ArrayList();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i <= <span class="number">10000</span>; i++) {</span><br><span class="line"> Person person = <span class="keyword">new</span> Person(i, <span class="string">"name"</span> + i);</span><br><span class="line"> persons.add(person);</span><br><span class="line"> }</span><br><span class="line">List<String> personList2 = persons.stream().</span><br><span class="line">map(Person::getName).limit(<span class="number">10</span>).skip(<span class="number">3</span>).collect(Collectors.toList());</span><br><span class="line"> System.out.println(personList2);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">private</span> <span class="class"><span class="keyword">class</span> <span class="title">Person</span> </span>{</span><br><span class="line"> <span class="keyword">public</span> <span class="keyword">int</span> no;</span><br><span class="line"> <span class="keyword">private</span> String name;</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">Person</span> <span class="params">(<span class="keyword">int</span> no, String name)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.no = no;</span><br><span class="line"> <span class="keyword">this</span>.name = name;</span><br><span class="line"> }</span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">getName</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(name);</span><br><span class="line"> <span class="keyword">return</span> name;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="sorted"><a href="#sorted" class="headerlink" title="sorted"></a>sorted</h3><blockquote><p>对 Stream 的排序通过 sorted 进行,它比数组的排序更强之处在于你可以首先对 Stream 进行各类 map、filter、limit、skip 甚至 distinct 来减少元素数量后,再排序,这能帮助程序明显缩短执行时间。</p></blockquote><ul><li>排序前进行 limit 和 skip<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><span class="line">List<Person> persons = <span class="keyword">new</span> ArrayList();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">1</span>; i <= <span class="number">5</span>; i++) {</span><br><span class="line"> Person person = <span class="keyword">new</span> Person(i, <span class="string">"name"</span> + i);</span><br><span class="line"> persons.add(person);</span><br><span class="line"> }</span><br><span class="line">List<Person> personList2 = persons.stream().limit(<span class="number">2</span>).sorted((p1, p2) -> p1.getName().compareTo(p2.getName())).collect(Collectors.toList());</span><br><span class="line">System.out.println(personList2);</span><br></pre></td></tr></table></figure></li></ul><h3 id="min-max-distinct"><a href="#min-max-distinct" class="headerlink" title="min/max/distinct"></a>min/max/distinct</h3><blockquote><p>min 和 max 的功能也可以通过对 Stream 元素先排序,再 findFirst 来实现,但前者的性能会更好,为 O(n),而 sorted 的成本是 O(n log n)。同时它们作为特殊的 reduce 方法被独立出来也是因为求最大最小值是很常见的操作。</p></blockquote><ul><li><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></pre></td><td class="code"><pre><span class="line">BufferedReader br = <span class="keyword">new</span> BufferedReader(<span class="keyword">new</span> FileReader(<span class="string">"c:\\SUService.log"</span>));</span><br><span class="line"><span class="keyword">int</span> longest = br.lines().</span><br><span class="line"> mapToInt(String::length).</span><br><span class="line"> max().</span><br><span class="line"> getAsInt();</span><br><span class="line">br.close();</span><br><span class="line">System.out.println(longest);</span><br></pre></td></tr></table></figure></li><li><p>找出全文的单词,转小写,并排序</p></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></pre></td><td class="code"><pre><span class="line">List<String> words = br.lines().</span><br><span class="line"> flatMap(line -> Stream.of(line.split(<span class="string">" "</span>))).</span><br><span class="line"> filter(word -> word.length() > <span class="number">0</span>).</span><br><span class="line"> map(String::toLowerCase).</span><br><span class="line"> distinct().</span><br><span class="line"> sorted().</span><br><span class="line"> collect(Collectors.toList());</span><br><span class="line">br.close();</span><br><span class="line">System.out.println(words);</span><br></pre></td></tr></table></figure><h3 id="Match"><a href="#Match" class="headerlink" title="Match"></a>Match</h3><p>Stream 有三个 match 方法,从语义上说:</p><pre><code>allMatch:Stream 中全部元素符合传入的 predicate,返回 trueanyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 truenoneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true</code></pre><blockquote><p>它们都不是要遍历全部元素才能返回结果。例如 allMatch 只要一个元素不满足条件,就 skip 剩下的所有元素,返回 false。</p></blockquote><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><span class="line">List<Person> persons = <span class="keyword">new</span> ArrayList();</span><br><span class="line">persons.add(<span class="keyword">new</span> Person(<span class="number">1</span>, <span class="string">"name"</span> + <span class="number">1</span>, <span class="number">10</span>));</span><br><span class="line">persons.add(<span class="keyword">new</span> Person(<span class="number">2</span>, <span class="string">"name"</span> + <span class="number">2</span>, <span class="number">21</span>));</span><br><span class="line">persons.add(<span class="keyword">new</span> Person(<span class="number">3</span>, <span class="string">"name"</span> + <span class="number">3</span>, <span class="number">34</span>));</span><br><span class="line">persons.add(<span class="keyword">new</span> Person(<span class="number">4</span>, <span class="string">"name"</span> + <span class="number">4</span>, <span class="number">6</span>));</span><br><span class="line">persons.add(<span class="keyword">new</span> Person(<span class="number">5</span>, <span class="string">"name"</span> + <span class="number">5</span>, <span class="number">55</span>));</span><br><span class="line"><span class="keyword">boolean</span> isAllAdult = persons.stream().</span><br><span class="line"> allMatch(p -> p.getAge() > <span class="number">18</span>);</span><br><span class="line">System.out.println(<span class="string">"All are adult? "</span> + isAllAdult);</span><br><span class="line"><span class="keyword">boolean</span> isThereAnyChild = persons.stream().</span><br><span class="line"> anyMatch(p -> p.getAge() < <span class="number">12</span>);</span><br><span class="line">System.out.println(<span class="string">"Any child? "</span> + isThereAnyChild);</span><br></pre></td></tr></table></figure><h2 id="用-Collectors-来进行-reduction-操作"><a href="#用-Collectors-来进行-reduction-操作" class="headerlink" title="用 Collectors 来进行 reduction 操作"></a>用 Collectors 来进行 reduction 操作</h2><blockquote><p>java.util.stream.Collectors 类的主要作用就是辅助进行各类有用的 reduction 操作,例如转变输出为 Collection,把 Stream 元素进行归组。</p></blockquote><ul><li>收集新的List<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">List<Integer> list = Stream.of(<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>).filter(p -> p > <span class="number">2</span>).collect(Collectors.toList());</span><br></pre></td></tr></table></figure></li></ul><h3 id="groupingBy-partitioningBy"><a href="#groupingBy-partitioningBy" class="headerlink" title="groupingBy/partitioningBy"></a>groupingBy/partitioningBy</h3><ul><li>按照年龄归组<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><span class="line">Map<Integer, List<Person>> personGroups = Stream.generate(<span class="keyword">new</span> PersonSupplier()).</span><br><span class="line"> limit(<span class="number">100</span>).</span><br><span class="line"> collect(Collectors.groupingBy(Person::getAge));</span><br><span class="line">Iterator it = personGroups.entrySet().iterator();</span><br><span class="line"><span class="keyword">while</span> (it.hasNext()) {</span><br><span class="line"> Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next();</span><br><span class="line"> System.out.println(<span class="string">"Age "</span> + persons.getKey() + <span class="string">" = "</span> + persons.getValue().size());</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li></ul><h2 id="reduce-与-collect区别"><a href="#reduce-与-collect区别" class="headerlink" title="reduce 与 collect区别"></a>reduce 与 collect区别</h2><blockquote><p>Stream.reduce,常用的方法有average, sum, min, max, count,返回单个的结果值,并且reduce操作每处理一个元素总是创建一个新值<br>Stream.collection与stream.reduce方法不同,Stream.collect修改现存的值,而不是每处理一个元素,创建一个新值</p></blockquote><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><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LambdaMapReduce</span> </span>{ </span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> List<User> users = Arrays.asList( </span><br><span class="line"> <span class="keyword">new</span> User(<span class="number">1</span>, <span class="string">"张三"</span>, <span class="number">12</span>,User.Sex.MALE), </span><br><span class="line"> <span class="keyword">new</span> User(<span class="number">2</span>, <span class="string">"李四"</span>, <span class="number">21</span>, User.Sex.FEMALE), </span><br><span class="line"> <span class="keyword">new</span> User(<span class="number">3</span>,<span class="string">"王五"</span>, <span class="number">32</span>, User.Sex.MALE), </span><br><span class="line"> <span class="keyword">new</span> User(<span class="number">4</span>, <span class="string">"赵六"</span>, <span class="number">32</span>, User.Sex.FEMALE)); </span><br><span class="line"> </span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{ </span><br><span class="line"> reduceAvg(); </span><br><span class="line"> reduceSum(); </span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//与stream.reduce方法不同,Stream.collect修改现存的值,而不是每处理一个元素,创建一个新值 </span></span><br><span class="line"> <span class="comment">//获取所有男性用户的平均年龄 </span></span><br><span class="line"> Averager averageCollect = users.parallelStream() </span><br><span class="line"> .filter(p -> p.getGender() == User.Sex.MALE) </span><br><span class="line"> .map(User::getAge) </span><br><span class="line"> .collect(Averager::<span class="keyword">new</span>, Averager::accept, Averager::combine); </span><br><span class="line"> </span><br><span class="line"> System.out.println(<span class="string">"Average age of male members: "</span> </span><br><span class="line"> + averageCollect.average()); </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//获取年龄大于12的用户列表 </span></span><br><span class="line"> List<User> list = users.parallelStream().filter(p -> p.age > <span class="number">12</span>) </span><br><span class="line"> .collect(Collectors.toList()); </span><br><span class="line"> System.out.println(list); </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//按性别统计用户数 </span></span><br><span class="line"> Map<User.Sex, Integer> map = users.parallelStream().collect( </span><br><span class="line"> Collectors.groupingBy(User::getGender, </span><br><span class="line"> Collectors.summingInt(p -> <span class="number">1</span>))); </span><br><span class="line"> System.out.println(map); </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//按性别获取用户名称 </span></span><br><span class="line"> Map<User.Sex, List<String>> map2 = users.stream() </span><br><span class="line"> .collect( </span><br><span class="line"> Collectors.groupingBy( </span><br><span class="line"> User::getGender, </span><br><span class="line"> Collectors.mapping(User::getName, </span><br><span class="line"> Collectors.toList()))); </span><br><span class="line"> System.out.println(map2); </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//按性别求年龄的总和 </span></span><br><span class="line"> Map<User.Sex, Integer> map3 = users.stream().collect( </span><br><span class="line"> Collectors.groupingBy(User::getGender, </span><br><span class="line"> Collectors.reducing(<span class="number">0</span>, User::getAge, Integer::sum))); </span><br><span class="line"> </span><br><span class="line"> System.out.println(map3); </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//按性别求年龄的平均值 </span></span><br><span class="line"> Map<User.Sex, Double> map4 = users.stream().collect( </span><br><span class="line"> Collectors.groupingBy(User::getGender, </span><br><span class="line"> Collectors.averagingInt(User::getAge))); </span><br><span class="line"> System.out.println(map4); </span><br><span class="line"> </span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="comment">// 注意,reduce操作每处理一个元素总是创建一个新值, </span></span><br><span class="line"> <span class="comment">// Stream.reduce适用于返回单个结果值的情况 </span></span><br><span class="line"> <span class="comment">//获取所有用户的平均年龄 </span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">reduceAvg</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="comment">// mapToInt的pipeline后面可以是average,max,min,count,sum </span></span><br><span class="line"> <span class="keyword">double</span> avg = users.parallelStream().mapToInt(User::getAge) </span><br><span class="line"> .average().getAsDouble(); </span><br><span class="line"> </span><br><span class="line"> System.out.println(<span class="string">"reduceAvg User Age: "</span> + avg); </span><br><span class="line"> } </span><br><span class="line"> </span><br><span class="line"> <span class="comment">//获取所有用户的年龄总和 </span></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">reduceSum</span><span class="params">()</span> </span>{ </span><br><span class="line"> <span class="keyword">double</span> sum = users.parallelStream().mapToInt(User::getAge) </span><br><span class="line"> .reduce(<span class="number">0</span>, (x, y) -> x + y); <span class="comment">// 可以简写为.sum() </span></span><br><span class="line"> </span><br><span class="line"> System.out.println(<span class="string">"reduceSum User Age: "</span> + sum); </span><br><span class="line"> } </span><br><span class="line">}</span><br></pre></td></tr></table></figure><h1 id="参考"><a href="#参考" class="headerlink" title="参考"></a>参考</h1><p> <a href="https://github.com/along101/java-components-test/blob/master/java8/src/test/java/com/yzl/test/java8/stream/StreamTest.java" target="_blank" rel="noopener">github/along101</a></p>]]></content>
<summary type="html">
<h1 id="stream简介"><a href="#stream简介" class="headerlink" title="stream简介"></a>stream简介</h1><p>Java 8 中的 Stream 是对集合(Collection)对象功能的增强,它专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。Stream API 借助于同样新出现的 Lambda 表达式,极大的提高编程效率和程序可读性。</p>
</summary>
<category term="java" scheme="http://along101.site/categories/java/"/>
<category term="java8" scheme="http://along101.site/tags/java8/"/>
<category term="stream" scheme="http://along101.site/tags/stream/"/>
</entry>
<entry>
<title>hexo博客使用Travis CI自动发布</title>
<link href="http://along101.site/2018/07/16/hexo-github-travis/"/>
<id>http://along101.site/2018/07/16/hexo-github-travis/</id>
<published>2018-07-16T11:09:13.000Z</published>
<updated>2018-07-20T03:10:04.405Z</updated>
<content type="html"><![CDATA[<h1 id="hexo-介绍"><a href="#hexo-介绍" class="headerlink" title="hexo 介绍"></a>hexo 介绍</h1><p>Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。</p><p>搭建hexo博客可以参考:<a href="http://techblog.ppdai.com/2018/07/06/20180706/" target="_blank" rel="noopener">http://techblog.ppdai.com/2018/07/06/20180706/</a></p><p><strong>本文主要介绍如何使用Travis CI自动发布blog,免除了在本地搭建nodejs、安装hexo和手工发布blog的繁琐过程。</strong></p><a id="more"></a><h1 id="思路"><a href="#思路" class="headerlink" title="思路"></a>思路</h1><p>hexo手工发布blog到github的原理是通过<code>hexo g</code>,将md文件生成静态的html文件,然后通过<code>hexo d</code>,将静态的html文件push到github仓库中。</p><p>这个过程都是在本地的nodejs环境中运行的,完全可以使用Travis来完成该过程。Travis是一个为GitHub服务的CI,可以把Travis看做一个远程服务器,它会监控你添加的GitHub库。如果有新的提交或Pull Request,就会触发一次在线编译,执行配置编译命令。</p><p>所以,可以将Hexo源码和blog静态页面放到github仓库的不同分支,在Hexo源码分支中添加Travis的配置,每当提交blog文档md后,Travis会自动生成静态页面并发push指定分支,完成blog的发布。</p><h1 id="hexo源码准备工作"><a href="#hexo源码准备工作" class="headerlink" title="hexo源码准备工作"></a>hexo源码准备工作</h1><ol><li><p>使用github搭建好个人blog</p><blockquote><p>参考<a href="http://techblog.ppdai.com/2018/07/06/20180706/" target="_blank" rel="noopener">http://techblog.ppdai.com/2018/07/06/20180706/</a></p></blockquote></li><li><p>创建分支存放hexo源码及blog源文件</p></li></ol><p>在username.github.io中创建分支可以命名问hexo,将hexo源代码已经blog源文件提交到该分支。master为blog生成后的html静态页面分支。</p><h1 id="Travis设置"><a href="#Travis设置" class="headerlink" title="Travis设置"></a>Travis设置</h1><p>1.进入官网<a href="https://www.travis-ci.org/" target="_blank" rel="noopener">https://www.travis-ci.org/</a>,使用github账号登录。<br>2.添加仓库<code>username.github.io</code></p><p><img src="/2018/07/16/hexo-github-travis/travis.png" alt="travis"></p><ol start="3"><li><p>在github添加Access Token,在travis中用此token push commit到github</p><p><img src="/2018/07/16/hexo-github-travis/github-token.png" alt="github-token"></p></li><li><p>在Travis设置页面将上一步生成的token添加到环境变量中,命名为myblog</p></li></ol><p><img src="/2018/07/16/hexo-github-travis/travis-token.png" alt="travis-token"></p><ol start="5"><li>在<code>username.github.io</code>仓库hexo分支中添加’.travis.yml’配置文件</li></ol><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><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><span class="line"><span class="attr">language:</span> <span class="string">node_js</span></span><br><span class="line"><span class="attr">node_js:</span> <span class="string">stable</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># S: Build Lifecycle</span></span><br><span class="line"><span class="attr">install:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">npm</span> <span class="string">install</span></span><br><span class="line"></span><br><span class="line"><span class="comment">#before_script:</span></span><br><span class="line"> <span class="comment"># - npm install -g gulp</span></span><br><span class="line"></span><br><span class="line"><span class="attr">script:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">hexo</span> <span class="string">g</span></span><br><span class="line"></span><br><span class="line"><span class="attr">after_script:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">cd</span> <span class="string">./public</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">git</span> <span class="string">init</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">git</span> <span class="string">config</span> <span class="string">user.name</span> <span class="string">"along101"</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">git</span> <span class="string">config</span> <span class="string">user.email</span> <span class="string">"[email protected]"</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">git</span> <span class="string">add</span> <span class="string">.</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">git</span> <span class="string">commit</span> <span class="bullet">-m</span> <span class="string">"Update docs"</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">git</span> <span class="string">push</span> <span class="bullet">--force</span> <span class="bullet">--quiet</span> <span class="string">"https://${myblog}@${GH_REF}"</span> <span class="attr">master:master</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># E: Build LifeCycle</span></span><br><span class="line"><span class="attr">branches:</span></span><br><span class="line"><span class="attr"> only:</span></span><br><span class="line"><span class="bullet"> -</span> <span class="string">hexo</span></span><br><span class="line"><span class="attr">env:</span></span><br><span class="line"><span class="attr"> global:</span></span><br><span class="line"><span class="attr"> - GH_REF:</span> <span class="string">github.com/along101/along101.github.io.git</span></span><br></pre></td></tr></table></figure><p><code>${myblog}</code>为上一步添加的环境变量,需要配置自己的</p><ul><li><code>user.name</code> github用户名</li><li><code>user.email</code> github注册邮箱</li><li><code>GH_REF</code> 仓库地址</li></ul><ol start="6"><li>提交该配置到git仓库,Travis就会自动发布blog,以后写blog,只需要在hexo分支的<code>source/_posts</code>目录下提交blog的md文档,Travis就会自动发布静态页面到master上</li></ol><p><img src="/2018/07/16/hexo-github-travis/travis-build.png" alt="travis-build"></p>]]></content>
<summary type="html">
<h1 id="hexo-介绍"><a href="#hexo-介绍" class="headerlink" title="hexo 介绍"></a>hexo 介绍</h1><p>Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。</p>
<p>搭建hexo博客可以参考:<a href="http://techblog.ppdai.com/2018/07/06/20180706/" target="_blank" rel="noopener">http://techblog.ppdai.com/2018/07/06/20180706/</a></p>
<p><strong>本文主要介绍如何使用Travis CI自动发布blog,免除了在本地搭建nodejs、安装hexo和手工发布blog的繁琐过程。</strong></p>
</summary>
<category term="工具" scheme="http://along101.site/categories/%E5%B7%A5%E5%85%B7/"/>
<category term="hexo" scheme="http://along101.site/tags/hexo/"/>
<category term="blog" scheme="http://along101.site/tags/blog/"/>
<category term="travis" scheme="http://along101.site/tags/travis/"/>
<category term="ci" scheme="http://along101.site/tags/ci/"/>
</entry>
<entry>
<title>Hexo+Github搭建个人博客</title>
<link href="http://along101.site/2018/07/15/hexo/"/>
<id>http://along101.site/2018/07/15/hexo/</id>
<published>2018-07-15T11:29:57.000Z</published>
<updated>2018-07-20T03:10:04.405Z</updated>
<content type="html"><![CDATA[<p>本篇作者<code>李志明</code>,转至 <a href="http://techblog.ppdai.com/2018/07/06/20180706/" target="_blank" rel="noopener">http://techblog.ppdai.com/2018/07/06/20180706/</a></p><h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>作为程序猿,相信大家都有过这样一个想法,搭建属于自己的博客网站,在上面写写技术文章,记录生活点滴,坚持下去会发现这是一件很有成就感的事情。最近刚好在学习这部分内容,深入进去后发现里面坑很多,为了节省大家的时间,少走一点弯路,我整理出了这篇文章供大家参考。</p><a id="more"></a><h2 id="为什么选择Hexo"><a href="#为什么选择Hexo" class="headerlink" title="为什么选择Hexo"></a>为什么选择Hexo</h2><p>之前在网上搜了一下目前比较流行的静态博客框架,最后目标锁定在Jekyll和Hexo上,两者都支持Markdown语法,这点我很喜欢,Jekyll基于Ruby实现,安装Jekyll需要搭建Ruby环境,而Hexo基于NodeJs实现,在Windows上安装NodeJs开发环境比Ruby简单,另外Hexo的主题相对来说更符合我的审美,所以最终选择了Hexo。</p><p>什么是Hexo?官网对它的介绍是:</p><blockquote><p>Hexo 是一个快速、简洁且高效的博客框架。Hexo 使用 Markdown(或其他渲染引擎)解析文章,在几秒内,即可利用靓丽的主题生成静态网页。</p></blockquote><h2 id="准备工作"><a href="#准备工作" class="headerlink" title="准备工作"></a>准备工作</h2><h3 id="安装Node-js"><a href="#安装Node-js" class="headerlink" title="安装Node.js"></a>安装Node.js</h3><p>下载地址:<a href="https://nodejs.org/en/download/" target="_blank" rel="noopener">https://nodejs.org/en/download/</a></p><p>推荐下载LTS版本的msi文件,默认64-bit,也可根据自己的Windows版本选择32-bit。</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/nodejs-install/nodejs-download.png" alt=""></p><p>保持默认设置即可,一路Next。安装完成后打开命令行窗口,输入命令:</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><span class="line">$ node -v</span><br><span class="line">$ npm -v</span><br></pre></td></tr></table></figure><p>结果如下图所示,则说明安装正确,可以进行下一步,如果不正确,请回头检查自己的安装过程。</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/nodejs-install/nodejs6.png" alt=""></p><h3 id="安装git"><a href="#安装git" class="headerlink" title="安装git"></a>安装git</h3><p>下载地址:<a href="https://git-scm.com/downloads" target="_blank" rel="noopener">https://git-scm.com/downloads</a></p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-install/git-download.png" alt=""></p><p>保持默认设置即可,一路Next。安装完成后打开命令行窗口,输入:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ git --version</span><br></pre></td></tr></table></figure><p>结果如下图所示,则说明安装正确,可以进行下一步,如果不正确,请回头检查自己的安装过程。</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-install/git11.png" alt=""></p><h3 id="github配置"><a href="#github配置" class="headerlink" title="github配置"></a>github配置</h3><p>第一步,注册一个github账号,记得点击邮箱中的验证链接,注册地址:<a href="https://github.com" target="_blank" rel="noopener">https://github.com</a></p><p>第二步,生成SSH keys</p><p>什么是ssh:ssh是Secure Shell(安全外壳协议)的缩写,建立在应用层和传输层基础上的安全协议。为了便于访问github,要生成ssh公钥,这样就不用每一次访问github都要输入用户名和密码。</p><p>1.本地成功安装git后,单击鼠标右键,选择Git Bash Here,打开git bash。</p><p>2.输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-keygen -t rsa -C <span class="string">"[email protected]"</span></span><br></pre></td></tr></table></figure><p>引号中的内容是你在github上的注册邮箱,之后一路回车,结果如图所示:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-sshkey1.png" alt=""></p><p>3.上一步已经成功的生成了ssh key,接下来输入:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">eval</span> <span class="string">"ssh-agent -s"</span></span><br></pre></td></tr></table></figure><p>然后输入:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-add ~/.ssh/id_rsa</span><br></pre></td></tr></table></figure><p>这一步可能会报错:<code>Could not open a connection to your authentication agent</code> ,这时直接输入:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-agent bash</span><br></pre></td></tr></table></figure><p>再次输入:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh-add ~/.ssh/id_rsa</span><br></pre></td></tr></table></figure><p>结果如图所示:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-sshkey2.png" alt=""></p><p>4.用cat命令查看key的内容:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ cat ~/.ssh/id_rsa.pub</span><br></pre></td></tr></table></figure><p>选中内容,右键复制备用,如图:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-sshkey3.png" alt=""></p><p>第三步,配置SSH keys</p><p>打开github页面,找到setting中的ssh keys,点击新增按钮,输入任意的title,将刚才复制的key粘贴进去保存即可。</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-config1.png" alt=""></p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-config2.png" alt=""></p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-config3.png" alt=""></p><p>第四步,测试</p><p>输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ ssh -T [email protected]</span><br></pre></td></tr></table></figure><p>结果如图:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-sshkey4.png" alt=""></p><p>到这里为止,准备工作就全部完成了。</p><h2 id="Hexo的安装与配置"><a href="#Hexo的安装与配置" class="headerlink" title="Hexo的安装与配置"></a>Hexo的安装与配置</h2><p>第一步,安装Hexo</p><p>打开命令行窗口,输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install -g hexo-cli</span><br></pre></td></tr></table></figure><p>安装完成后输入:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo version</span><br></pre></td></tr></table></figure><p>结果如下图所示,则说明安装正确。</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo2.png" alt=""></p><p>如果报错:<code>'hexo'不是内部或外部命令,也不是可运行的程序或批处理文件</code> ,则需要检查环境变量配置是否正确,如下图所示,编辑Path变量值,在结尾处加上:<code>C:\Program Files\nodejs\node_global;</code>(文件hexo.cmd所在目录)</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/nodejs-install/nodejs7.png" alt=""></p><p>第二步,初始化Hexo</p><p>进入任意目录,比如F盘,然后指定一个文件夹名,这里以blog为例,命令如下:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo init blog</span><br></pre></td></tr></table></figure><p>结果如下图所示,F盘下会多出一个blog文件夹:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo3.png" alt=""></p><p>接下来进入blog目录:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ <span class="built_in">cd</span> blog</span><br></pre></td></tr></table></figure><p>第三步,安装必要的依赖</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install</span><br></pre></td></tr></table></figure><p>第四步,生成静态文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo generate</span><br></pre></td></tr></table></figure><p>该命令的简写形式为:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo g</span><br></pre></td></tr></table></figure><p>执行完毕后会在blog目录下生成一个public文件夹,里面包含了博客网站的所有静态资源。</p><p>第五步,启动服务器</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo server</span><br></pre></td></tr></table></figure><p>该命令的简写形式为:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo s</span><br></pre></td></tr></table></figure><p>默认情况下,访问地址为:<a href="http://localhost:4000/" target="_blank" rel="noopener">http://localhost:4000/</a></p><p>另外也可以指定端口(比如8000):</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo s -p 8000</span><br></pre></td></tr></table></figure><p>第六步,验证</p><p>在浏览器中打开上面的地址,你将会看到:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo1.png" alt=""></p><p>到这里为止,Hexo的安装与相关配置就全部完成了。</p><h2 id="将Hexo与Github-Pages联系起来"><a href="#将Hexo与Github-Pages联系起来" class="headerlink" title="将Hexo与Github Pages联系起来"></a>将Hexo与Github Pages联系起来</h2><p>第一步,创建代码库</p><p>1.登录github,点击页面右上角的加号,选择New repository</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-config4.png" alt=""></p><p>2.在Repository name中填写 <code>yourname.github.io</code> ,注意这里的yourname指的是你的github用户名,如果你的名字是kirito,那就填 <code>kirito.github.io</code> ,Description中可以填一些简单的描述,不写也没关系,然后点击Create repository</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-config5.png" alt=""></p><p>3.正确创建之后,你会看到如下界面:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/git-config/git-config6.png" alt=""></p><p>第二步,编辑站点配置文件</p><p>打开blog目录下的_config.yml文件,编辑deploy模块,内容如下:</p><figure class="highlight plain"><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><span class="line">deploy:</span><br><span class="line"> type: git</span><br><span class="line"> repo: [email protected]:lizhiming1995/yourname.github.io.git</span><br><span class="line"> branch: master</span><br></pre></td></tr></table></figure><p>注意这里的repo地址应该换成你第一步创建的代码库的地址。</p><p>第三步,安装一个扩展</p><p>进入blog目录,打开命令行窗口,输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install hexo-deployer-git --save</span><br></pre></td></tr></table></figure><p>安装完成后,就可以一键部署到github上了。</p><p>第四步,部署</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo deploy</span><br></pre></td></tr></table></figure><p>该命令的简写形式为:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo d</span><br></pre></td></tr></table></figure><p>执行结果如图所示:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo4.png" alt=""></p><p>这样你public目录下的静态文件就上传到你的代码库中了。</p><p>第五步,激活GitHub Pages</p><p>打开代码库的Settings页面,找到GitHub Pages,选择master branch,然后点击Save按钮,如图:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo5.png" alt=""></p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo6.png" alt=""></p><p>最后会提示你:<code>Your site is ready to be published at http://yourname.github.io/</code></p><p>这就是你的博客地址了,任何人都可以访问哦。</p><h2 id="绑定自己的域名"><a href="#绑定自己的域名" class="headerlink" title="绑定自己的域名"></a>绑定自己的域名</h2><p>第一步,在万网、腾讯云、阿里云等提供域名注册的域名服务商处购买一个域名,这里以阿里云为例,购买地址:<a href="https://wanwang.aliyun.com/" target="_blank" rel="noopener">https://wanwang.aliyun.com/</a></p><p>第二步,打开控制台,给域名添加DNS解析</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/dns1.png" alt=""></p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/dns2.png" alt=""></p><p>添加两条解析记录,记录类型为CNAME,主机记录分别填@和www,记录值填之前GitHub Pages提供的域名,注意没有http的前缀,如下图:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/dns3.png" alt=""></p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/dns4.png" alt=""></p><p>添加完后别人用www和不用www都能访问你的网站。</p><p>第三步,在blog目录的source文件夹下创建一个CNAME文件,记住不要有文件后缀名,编辑CNAME文件,里面写你购买的域名,例如 <code>yourname.com</code> ,记住不要有www,创建完成后每一次执行 <code>hexo g</code> 都会在public文件夹下生成CNAME文件,方便后面的部署</p><p>第四步,在blog目录下打开命令行窗口,运行 <code>hexo g</code> ,再运行 <code>hexo d</code></p><p>第五步,在浏览器输入你购买的域名,你会发现该域名已经指向了你在github上的博客地址</p><p>注意:设置域名解析需要几分钟的时间,若完成以上步骤依然无法访问,请过几分钟再尝试</p><h2 id="Hexo入门"><a href="#Hexo入门" class="headerlink" title="Hexo入门"></a>Hexo入门</h2><p>我们先来看一下blog的目录结构:</p><figure class="highlight plain"><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><span class="line">+ blog</span><br><span class="line"> + public //静态资源文件夹,内容会推送到代码库</span><br><span class="line"> + scaffolds //模板文件夹,新建文章时,Hexo会根据模板来建立文件</span><br><span class="line"> + source //资源文件夹,Markdown和HTML文件会被解析并放到public文件夹,而其他文件会被拷贝过去</span><br><span class="line"> + themes //主题文件夹,Hexo会根据主题来生成静态页面</span><br><span class="line"> - _config.yml //网站的配置信息,可以在此配置大部分的参数</span><br><span class="line"> - package.json //应用程序的信息和依赖关系</span><br></pre></td></tr></table></figure><p>方便起见,我们把网站的语言设置为中文,编辑blog目录下的_config.yml文件,将language这一项设置为 <code>language: zh-CN</code>(参考blog/themes/landscape/languages目录),将url这一项设置为 <code>url: http://yourname.com</code>(你购买的域名,若未购买可以用 <code>http://yourname.github.io</code> 代替),其他配置项请根据自己的需要进行设置。</p><p>接下来新建一篇文章:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new [layout] <title></span><br></pre></td></tr></table></figure><p>layout可选值有:draft(草稿)、page(页面)、post(文章),对应模板文件夹中的3个文件,如果没有设置layout的话,默认使用_config.yml中的default_layout参数(默认值post)代替。若标题包含空格,请使用引号括起来。</p><p>现在,我们来新建一篇名为test的文章,输入以下命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new <span class="built_in">test</span></span><br></pre></td></tr></table></figure><p>结果在source/_posts目录下生成了test.md文件,内容如下:</p><figure class="highlight plain"><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><span class="line">---</span><br><span class="line">title: test</span><br><span class="line">date: 2018-06-23 19:14:56</span><br><span class="line">tags:</span><br><span class="line">---</span><br></pre></td></tr></table></figure><p>这里给出Front-matter的概念,Front-matter是文件最上方以 <code>---</code> 分隔的区域,用于指定文件的变量。</p><p>常见参数:title(标题)、date(创建日期)、tags(标签)、categories(分类),只有文章(post)支持标签和分类参数,建议文章分类只写一个,标签可以有多个,写法为 <code>tags: [tag1,tag2,tag3]</code> ,注意每个参数的冒号后面都应该有一个空格,这一点同样体现在_config.yml文件中</p><p>编辑test.md文件,内容如下:</p><figure class="highlight plain"><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><span class="line">---</span><br><span class="line">title: test</span><br><span class="line">date: 2018-06-23 19:14:56</span><br><span class="line">tags: [tag1,tag2,tag3]</span><br><span class="line">categories: java</span><br><span class="line">---</span><br><span class="line">文章正文</span><br></pre></td></tr></table></figure><p>保存后刷新页面,通常情况下页面会自动更新,若修改没有生效,则需要重新执行以下命令:</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><span class="line">$ hexo g</span><br><span class="line">$ hexo s</span><br></pre></td></tr></table></figure><p>这里再介绍一个命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo clean</span><br></pre></td></tr></table></figure><p>它的作用是清除缓存文件 (db.json) 和已生成的静态文件 (public)。在某些情况下(尤其是更换主题后),如果你发现对站点的更改无论如何也不生效,你可能需要运行该命令。</p><p>打开网站首页,你会看到刚才设置的标签和分类生效了:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo7.png" alt=""></p><p>发现Hello World这篇文章的内容被折叠起来了吗,很简单,只需要在文章正文合适的地方加上 <code><!--more--></code> 这一行代码就搞定了。</p><p>菜单栏只有Home和Archives?没关系,我们可以加个页面(page),这里以about为例,在blog目录下打开命令行窗口,输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new page about</span><br></pre></td></tr></table></figure><p>结果会在source目录下生成about文件夹,里面包含一个index.md文件,文件内容就是about页面的内容。</p><p>还没有结束,编辑blog/themes/landscape目录下的_config.yml文件,修改menu的配置为:</p><figure class="highlight plain"><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><span class="line">menu:</span><br><span class="line"> 首页: /</span><br><span class="line"> 归档: /archives</span><br><span class="line"> 关于: /about</span><br></pre></td></tr></table></figure><p>保存刷新页面,你会看到导航栏里多了“关于”,点进去就是about页面啦,目前只有一个标题,内容待编辑,注意页面是不支持设置标签和分类的,只有文章才支持。</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo8.png" alt=""></p><p>最后,我们来总结一下发布文章的流程:</p><p>第一步,<code>hexo new <title></code> 生成一篇文章,这里的title指文件名,不建议使用中文</p><p>第二步,编辑文章,修改title、tags等参数,这里的title指文章标题,可以使用中文</p><p>第三步,<code>hexo s</code> 本地预览效果,不满意继续修改</p><p>第四步,<code>hexo g</code> 生成静态文件</p><p>第五步,<code>hexo d</code> 将静态文件推送至代码库</p><p>第四步和第五步可以合并成一条命令,<code>hexo d -g</code> ,表示部署之前预先生成静态文件。修改配置与发布文章的流程相似,最后都需要执行第三四五步。</p><h2 id="Hexo进阶"><a href="#Hexo进阶" class="headerlink" title="Hexo进阶"></a>Hexo进阶</h2><h3 id="添加RSS订阅功能"><a href="#添加RSS订阅功能" class="headerlink" title="添加RSS订阅功能"></a>添加RSS订阅功能</h3><p>RSS是在线共享内容的一种简易方式,也叫简易信息聚合,全称Really Simple Syndication。当网站内容更新时,可以通过订阅RSS源在RSS阅读器上获取更新的信息,大多数的内容提供网站都会提供RSS订阅功能,方便用户去获取最新的内容。</p><p>1.安装feed插件</p><p>Hexo有一个专门生成RSS文件的插件 <code>hexo-generator-feed</code> ,进入blog目录,打开命令行窗口,输入命令:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ npm install hexo-generator-feed --save</span><br></pre></td></tr></table></figure><p>2.启用插件</p><p>编辑blog目录下的_config.yml文件,添加如下内容:</p><figure class="highlight plain"><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><span class="line"># Extensions</span><br><span class="line">Plugins:</span><br><span class="line">- hexo-generator-feed</span><br><span class="line"># Feed Atom</span><br><span class="line">feed:</span><br><span class="line"> type: atom</span><br><span class="line"> path: atom.xml</span><br><span class="line"> limit: 20</span><br></pre></td></tr></table></figure><p>3.生成RSS</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo g</span><br></pre></td></tr></table></figure><p>如果生成了atom.xml就表示成功了:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo9.png" alt=""></p><p>在浏览器中打开 <a href="http://localhost:4000/atom.xml" target="_blank" rel="noopener">http://localhost:4000/atom.xml</a> ,你会看到订阅功能已开启:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo10.png" alt=""></p><p>4.部署</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo d</span><br></pre></td></tr></table></figure><p>5.使用RSS订阅功能</p><p>这里以Office的Outlook邮箱为例,订阅地址假设为 <code>http://spring2go.com/atom.xml</code>,如图:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo11.png" alt=""></p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo12.png" alt=""></p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo13.png" alt=""></p><h3 id="添加站点地图"><a href="#添加站点地图" class="headerlink" title="添加站点地图"></a>添加站点地图</h3><p>站点地图是一种文件,你可以通过该文件列出你网站上的网页,从而将你网站内容的组织架构告知Google和其他搜索引擎。Googlebot等搜索引擎网页抓取工具会读取此文件,以便更加智能地抓取你的网站。</p><p>1.安装sitemap插件</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><span class="line">$ npm install hexo-generator-sitemap --save</span><br><span class="line">$ npm install hexo-generator-baidu-sitemap --save</span><br></pre></td></tr></table></figure><p>2.编辑blog目录下的_config.yml文件,添加如下内容:</p><figure class="highlight plain"><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><span class="line">Plugins:</span><br><span class="line">- hexo-generator-sitemap</span><br><span class="line">- hexo-generator-baidu-sitemap</span><br><span class="line"></span><br><span class="line">sitemap:</span><br><span class="line"> path: sitemap.xml</span><br><span class="line"></span><br><span class="line">baidusitemap:</span><br><span class="line"> path: baidusitemap.xml</span><br></pre></td></tr></table></figure><p>3.生成站点地图文件</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo g</span><br></pre></td></tr></table></figure><p>如果生成了sitemap.xml和baidusitemap.xml就表示成功了:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo15.png" alt=""></p><p>4.部署</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo d</span><br></pre></td></tr></table></figure><p>5.确认博客是否被收录</p><p>在百度或者谷歌输入下面格式的内容,如果能搜索到就说明被收录,否则未收录,可能需要等上一段时间。</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">site:xxx.com</span><br></pre></td></tr></table></figure><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo14.png" alt=""></p><h3 id="使用模板功能"><a href="#使用模板功能" class="headerlink" title="使用模板功能"></a>使用模板功能</h3><p>现在我们生成的每一篇新文章都只有title、date、tags三个参数,通常情况下我们还会有categories参数和一些自定义的参数(如何使用自定义参数我们后面讲),每次都要手动加上这些参数会浪费很多时间,这时候模板的作用就出来了。</p><p>打开scaffolds文件夹,你会看到里面有draft、page、post三个模板,对应草稿、页面、文章,我们日常使用最多的就是文章,所以这里以文章为例,其他两个模板请根据自己的需要进行修改。</p><p>模板的参数是可以设置默认值的,我们假设categories参数的默认值为 <code>随笔</code> ,然后自定义一个参数 <code>author</code> ,默认值为 <code>kirito</code> ,因为每篇文章的标签是不确定的,所以这里不进行设置,编辑post.md文件,内容如下:</p><figure class="highlight plain"><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><span class="line">---</span><br><span class="line">title: {{ title }}</span><br><span class="line">date: {{ date }}</span><br><span class="line">tags: </span><br><span class="line">categories: 随笔</span><br><span class="line">author: kirito</span><br><span class="line">---</span><br></pre></td></tr></table></figure><p>让我们用模板生成一篇新文章:</p><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">$ hexo new test2</span><br></pre></td></tr></table></figure><p>打开blog/source/_posts目录下的test2.md文件,可以看到以下内容:</p><figure class="highlight plain"><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><span class="line">---</span><br><span class="line">title: test2</span><br><span class="line">categories: 随笔</span><br><span class="line">author: kirito</span><br><span class="line">date: 2018-06-27 22:02:33</span><br><span class="line">tags:</span><br><span class="line">---</span><br></pre></td></tr></table></figure><p>接下来我们只需要写好文章,设置一下tags就可以发布了。</p><h3 id="使用自定义参数"><a href="#使用自定义参数" class="headerlink" title="使用自定义参数"></a>使用自定义参数</h3><p>文章参数里的title、date、categories和tags都在页面上有所展示,那我们自定义的参数该如何使用和展示呢?</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo16.png" alt=""></p><p>通过控制台我们可以看到,每篇文章都是一个 <code>class="article article-type-post"</code> 的 <code>article</code> 标签,结构如下:</p><figure class="highlight html"><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><span class="line"><span class="tag"><<span class="name">article</span> <span class="attr">id</span>=<span class="string">"post-test"</span> <span class="attr">class</span>=<span class="string">"article article-type-post"</span> <span class="attr">itemscope</span>=<span class="string">""</span> <span class="attr">itemprop</span>=<span class="string">"blogPost"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"article-meta"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">a</span> <span class="attr">href</span>=<span class="string">"/2018/06/23/test/"</span> <span class="attr">class</span>=<span class="string">"article-date"</span>></span>...<span class="tag"></<span class="name">a</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"article-category"</span>></span>...<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"article-inner"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">header</span> <span class="attr">class</span>=<span class="string">"article-header"</span>></span>...<span class="tag"></<span class="name">header</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"article-entry"</span> <span class="attr">itemprop</span>=<span class="string">"articleBody"</span>></span>...<span class="tag"></<span class="name">div</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">footer</span> <span class="attr">class</span>=<span class="string">"article-footer"</span>></span>...<span class="tag"></<span class="name">footer</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">article</span>></span></span><br></pre></td></tr></table></figure><p>title、date、categories和tags的值分别显示于article-header、article-date、article-category和article-footer,要使用自定义参数,我们需要修改主题的源文件,打开 <code>blog/themes/landscape/layout/_partial</code> 目录下的article.ejs文件,可以看到代码中的标签与class名都与上面一致,接下来我们让作者的名字显示在分类右边,编辑article-meta模块的代码:</p><figure class="highlight html"><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><span class="line"><span class="tag"><<span class="name">div</span> <span class="attr">class</span>=<span class="string">"article-meta"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">%-</span> <span class="attr">partial</span>('<span class="attr">post</span>/<span class="attr">date</span>', {<span class="attr">class_name:</span> '<span class="attr">article-date</span>', <span class="attr">date_format:</span> <span class="attr">null</span>}) %></span></span><br><span class="line"> <span class="tag"><<span class="name">%-</span> <span class="attr">partial</span>('<span class="attr">post</span>/<span class="attr">category</span>') %></span></span><br><span class="line"> <span class="tag"><<span class="name">div</span> <span class="attr">style</span>=<span class="string">"letter-spacing:2px;color:#999;line-height:1em;"</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">%-</span> <span class="attr">post.author</span> %></span></span><br><span class="line"> <span class="tag"></<span class="name">div</span>></span></span><br><span class="line"><span class="tag"></<span class="name">div</span>></span></span><br></pre></td></tr></table></figure><p>这里为了方便直接将样式写在了div标签里,更好的做法是为div添加一个class,将样式写进 <code>landscape/source/css/_partial</code><br> 目录下的article.styl文件。当然,自定义标签可以用在其他地方,样式也可以根据你的喜好来定制。</p><p>让我们打开浏览器来看下效果,你会发现文章的自定义标签生效了:</p><p><img src="https://images-1256966106.cos.ap-shanghai.myqcloud.com/hexo/hexo17.png" alt=""></p><p>值得注意的是,我们刚才修改的文件是article.ejs,这是跟主题有关的,换一个主题,也许文件的路径和名字都变了,甚至格式也不再是ejs而是swig,不过修改文件的思路都是一样的,明确自己要修改哪一个模块,然后到主题的相关目录下,模仿源代码的语法进行修改,最后记住,源文件里使用Tab键会导致修改的代码无效或者报错,请使用空格。</p><p>那么教程到这里就结束了,快来试试搭建自己的博客吧,有什么问题可以在评论区留言~</p><p>ps:后面我会单独整理一篇关于Hexo主题的文章,不用期待…</p>]]></content>
<summary type="html">
<p>本篇作者<code>李志明</code>,转至 <a href="http://techblog.ppdai.com/2018/07/06/20180706/" target="_blank" rel="noopener">http://techblog.ppdai.com/2018/07/06/20180706/</a></p>
<h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><p>作为程序猿,相信大家都有过这样一个想法,搭建属于自己的博客网站,在上面写写技术文章,记录生活点滴,坚持下去会发现这是一件很有成就感的事情。最近刚好在学习这部分内容,深入进去后发现里面坑很多,为了节省大家的时间,少走一点弯路,我整理出了这篇文章供大家参考。</p>
</summary>
<category term="工具" scheme="http://along101.site/categories/%E5%B7%A5%E5%85%B7/"/>
<category term="Hexo" scheme="http://along101.site/tags/Hexo/"/>
<category term="Github" scheme="http://along101.site/tags/Github/"/>
<category term="博客" scheme="http://along101.site/tags/%E5%8D%9A%E5%AE%A2/"/>
</entry>
<entry>
<title>spring-cloud-openfeign 深度分析</title>
<link href="http://along101.site/2018/06/15/spring-cloud-openfeign/"/>
<id>http://along101.site/2018/06/15/spring-cloud-openfeign/</id>
<published>2018-06-15T12:03:13.000Z</published>
<updated>2018-07-20T03:10:04.413Z</updated>
<content type="html"><![CDATA[<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p><a href="https://github.com/OpenFeign/feign" target="_blank" rel="noopener">feign</a>是一个声明试的HTTP客户端,spring-cloud-openfeign将feign集成到spring boot中,在接口上通过注解声明Rest协议,将http调用转换为接口方法的调用,使得客户端调用http服务更加简单。</p><p>当前spring cloud最新稳定版本是Edgware,feign在其集成的<a href="https://github.com/spring-cloud/spring-cloud-netflix" target="_blank" rel="noopener">spring-cloud-netflix</a> 1.4.0.RELEASE版本中。</p><blockquote><p>spring cloud下一个版本是Finchley,将会单独集成<a href="https://github.com/spring-cloud/spring-cloud-openfeign" target="_blank" rel="noopener">spring-cloud-openfeign</a></p></blockquote><a id="more"></a><h1 id="demo"><a href="#demo" class="headerlink" title="demo"></a>demo</h1><p>我们来看个简单的例子。源代码链接:<a href="https://github.com/along101/spring-boot-test/tree/master/feign-test" target="_blank" rel="noopener">https://github.com/along101/spring-boot-test/tree/master/feign-test</a></p><h2 id="服务端代码"><a href="#服务端代码" class="headerlink" title="服务端代码"></a>服务端代码</h2><p>使用spring boot编写一个简单的Rest服务<br><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><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloController</span> <span class="keyword">implements</span> <span class="title">HelloService</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> String <span class="title">hello</span><span class="params">(@RequestParam(<span class="string">"name"</span>)</span> String name) </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="string">"Hello "</span> + name;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>接口代码:<br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/test"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">HelloService</span> </span>{</span><br><span class="line"> <span class="meta">@RequestMapping</span>(value = <span class="string">"/hello1"</span>, method = RequestMethod.GET)</span><br><span class="line"> <span class="function">String <span class="title">hello</span><span class="params">(@RequestParam(<span class="string">"name"</span>)</span> String name)</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>代码很简单,通过springMVC注解在接口HelloService上声明Rest服务,HelloController被@RestController注解声明为一个Rest服务。</p><p>启动spring boot 就可通过浏览器访问<a href="http://localhost:8080/test/hello1?name=ppdai得到返回Hello" target="_blank" rel="noopener">http://localhost:8080/test/hello1?name=ppdai得到返回Hello</a> ppdai。</p><h2 id="客户端代码"><a href="#客户端代码" class="headerlink" title="客户端代码"></a>客户端代码</h2><p>客户端pom中需要加入spring-cloud-starter-feign的依赖<br><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><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><span class="line"><span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> ...</span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"><span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-starter-feign<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"></span><br><span class="line"> <span class="tag"><<span class="name">dependencyManagement</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-dependencies<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>Camden.SR7<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">type</span>></span>pom<span class="tag"></<span class="name">type</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">scope</span>></span>import<span class="tag"></<span class="name">scope</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencies</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependencyManagement</span>></span></span><br></pre></td></tr></table></figure></p><p>在客户端中新建一个HelloClient接口继承服务端HelloService接口:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//在spring boot配置文件中配置remote.hello.service.host=http://localhost:8080</span></span><br><span class="line"><span class="meta">@FeignClient</span>(value = <span class="string">"HELLO-SERVICE"</span>, url = <span class="string">"${remote.hello.service.host}"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">HelloClient</span> <span class="keyword">extends</span> <span class="title">HelloService</span> </span>{</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>HelloClient接口上注解@FeignClient,声明为Feign的客户端,参数url指定服务端地址。<br>在spring boot启动类上增加注解@EnableFeignClients</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><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="meta">@EnableFeignClients</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FeignClientApplication</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(FeignClientApplication.class, args);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>注意,HelloClient接口需要在启动类package或者子package之下。<br>编写测试类测试:<br><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><span class="line"><span class="meta">@RunWith</span>(SpringJUnit4ClassRunner.class)</span><br><span class="line"><span class="meta">@SpringBootTest</span>(classes = FeignClientApplication.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">HelloClientTest</span> </span>{</span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> <span class="keyword">private</span> HelloClient helloClient;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Test</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">testClient</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> String result = helloClient.hello(<span class="string">"ppdai"</span>);</span><br><span class="line"> System.out.println(result);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>启动服务端后,运行该测试类,在控制台会打印出<code>Hello ppdai</code></p><h1 id="原理分析"><a href="#原理分析" class="headerlink" title="原理分析"></a>原理分析</h1><p>看到客户端测试类中,我们只用了一行代码,就能完成对远程Rest服务的调用,相当的简单。为什么这么神奇,这几段代码是如何做到的呢?</p><h2 id="EnableFeignClients-注解声明客户端接口"><a href="#EnableFeignClients-注解声明客户端接口" class="headerlink" title="@EnableFeignClients 注解声明客户端接口"></a>@EnableFeignClients 注解声明客户端接口</h2><p>入口是启动类上的注解@EnableFeignClients,源代码:</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><span class="line"><span class="meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="meta">@Target</span>(ElementType.TYPE)</span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="meta">@Import</span>(FeignClientsRegistrar.class)</span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> EnableFeignClients {</span><br><span class="line"><span class="comment">//basePackages的别名</span></span><br><span class="line">String[] value() <span class="keyword">default</span> {};</span><br><span class="line"><span class="comment">//声明基础包,spring boot启动后,会扫描该包下被@FeignClient注解的接口</span></span><br><span class="line">String[] basePackages() <span class="keyword">default</span> {};</span><br><span class="line"><span class="comment">//声明基础包的类,通过该类声明基础包</span></span><br><span class="line">Class<?>[] basePackageClasses() <span class="keyword">default</span> {};</span><br><span class="line"><span class="comment">//默认配置类</span></span><br><span class="line">Class<?>[] defaultConfiguration() <span class="keyword">default</span> {};</span><br><span class="line"><span class="comment">//直接声明的客户端接口类</span></span><br><span class="line">Class<?>[] clients() <span class="keyword">default</span> {};</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>@EnableFeignClients的参数声明客户端接口的位置和默认的配置类。</p><h3 id="FeignClient注解,将接口声明为Feign客户端"><a href="#FeignClient注解,将接口声明为Feign客户端" class="headerlink" title="@FeignClient注解,将接口声明为Feign客户端"></a>@FeignClient注解,将接口声明为Feign客户端</h3><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Target</span>(ElementType.TYPE)</span><br><span class="line"><span class="meta">@Retention</span>(RetentionPolicy.RUNTIME)</span><br><span class="line"><span class="meta">@Documented</span></span><br><span class="line"><span class="keyword">public</span> <span class="meta">@interface</span> FeignClient {</span><br><span class="line"></span><br><span class="line"><span class="meta">@AliasFor</span>(<span class="string">"name"</span>)</span><br><span class="line"><span class="function">String <span class="title">value</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"><span class="comment">//名称,对应与eureka上注册的应用名</span></span><br><span class="line"><span class="meta">@AliasFor</span>(<span class="string">"value"</span>)</span><br><span class="line"><span class="function">String <span class="title">name</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"><span class="comment">//生成spring bean的qualifier</span></span><br><span class="line"><span class="function">String <span class="title">qualifier</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"><span class="comment">//http服务的url</span></span><br><span class="line"><span class="function">String <span class="title">url</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">decode404</span><span class="params">()</span> <span class="keyword">default</span> <span class="keyword">false</span></span>;</span><br><span class="line"><span class="comment">//配置类,这里设置的配置类是Spring Configuration,将会在FeignContext中创建内部声明的Bean,用于不同的客户端进行隔离</span></span><br><span class="line">Class<?>[] configuration() <span class="keyword">default</span> {};</span><br><span class="line"><span class="comment">//声明hystrix调用失败后的方法</span></span><br><span class="line">Class<?> fallback() <span class="keyword">default</span> <span class="keyword">void</span>.class;</span><br><span class="line">Class<?> fallbackFactory() <span class="keyword">default</span> <span class="keyword">void</span>.class;</span><br><span class="line"><span class="function">String <span class="title">path</span><span class="params">()</span> <span class="keyword">default</span> ""</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="FeignClientsRegistrar-注册客户端"><a href="#FeignClientsRegistrar-注册客户端" class="headerlink" title="FeignClientsRegistrar 注册客户端"></a>FeignClientsRegistrar 注册客户端</h2><p>@EnableFeignClients注解上被注解了@Import(FeignClientsRegistrar.class),@Import注解的作用是将指定的类作为Bean注入到Spring Context中,我们再来看被引入的FeignClientsRegistrar</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><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FeignClientsRegistrar</span> <span class="keyword">implements</span> <span class="title">ImportBeanDefinitionRegistrar</span>,</span></span><br><span class="line"><span class="class"><span class="title">ResourceLoaderAware</span>, <span class="title">BeanClassLoaderAware</span> </span>{</span><br><span class="line">。。。</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerBeanDefinitions</span><span class="params">(AnnotationMetadata metadata,</span></span></span><br><span class="line"><span class="function"><span class="params">BeanDefinitionRegistry registry)</span> </span>{</span><br><span class="line">registerDefaultConfiguration(metadata, registry);</span><br><span class="line">registerFeignClients(metadata, registry);</span><br><span class="line">}</span><br><span class="line">。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>FeignClientsRegistrar类实现了3个接口:</p><ul><li>接口ResourceLoaderAware用于注入ResourceLoader</li><li>接口BeanClassLoaderAware用于注入ClassLoader</li><li>接口ImportBeanDefinitionRegistrar用于动态向Spring Context中注册bean</li></ul><p>ImportBeanDefinitionRegistrar接口方法registerBeanDefinitions有两个参数</p><ul><li>AnnotationMetadata 包含被@Import注解类的信息</li></ul><blockquote><p>这里 @Import注解在@EnableFeignClients上,@EnableFeignClients注解在spring boot启动类上,AnnotationMetadata拿到的是spring boot启动类的相关信息</p></blockquote><ul><li>BeanDefinitionRegistry bean定义注册中心</li></ul><h2 id="registerDefaultConfiguration方法,注册默认配置"><a href="#registerDefaultConfiguration方法,注册默认配置" class="headerlink" title="registerDefaultConfiguration方法,注册默认配置"></a>registerDefaultConfiguration方法,注册默认配置</h2><p>registerDefaultConfiguration方法代码:</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerDefaultConfiguration</span><span class="params">(AnnotationMetadata metadata,</span></span></span><br><span class="line"><span class="function"><span class="params">BeanDefinitionRegistry registry)</span> </span>{</span><br><span class="line"><span class="comment">//获取@EnableFeignClients注解参数</span></span><br><span class="line">Map<String, Object> defaultAttrs = metadata</span><br><span class="line">.getAnnotationAttributes(EnableFeignClients.class.getName(), <span class="keyword">true</span>);</span><br><span class="line"><span class="comment">//如果参数中包含defaultConfiguration</span></span><br><span class="line"><span class="keyword">if</span> (defaultAttrs != <span class="keyword">null</span> && defaultAttrs.containsKey(<span class="string">"defaultConfiguration"</span>)) {</span><br><span class="line">String name;</span><br><span class="line"><span class="keyword">if</span> (metadata.hasEnclosingClass()) {</span><br><span class="line">name = <span class="string">"default."</span> + metadata.getEnclosingClassName();</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line">name = <span class="string">"default."</span> + metadata.getClassName();</span><br><span class="line">}</span><br><span class="line"><span class="comment">//注册客户端的配置Bean</span></span><br><span class="line">registerClientConfiguration(registry, name,</span><br><span class="line">defaultAttrs.get(<span class="string">"defaultConfiguration"</span>));</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>取出@EnableFeignClients注解参数defaultConfiguration,注册到spring Context中。registerClientConfiguration方法代码如下:</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><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerClientConfiguration</span><span class="params">(BeanDefinitionRegistry registry, Object name,</span></span></span><br><span class="line"><span class="function"><span class="params">Object configuration)</span> </span>{</span><br><span class="line"><span class="comment">// 创建一个BeanDefinitionBuilder,注册bean的类为FeignClientSpecification</span></span><br><span class="line">BeanDefinitionBuilder builder = BeanDefinitionBuilder</span><br><span class="line">.genericBeanDefinition(FeignClientSpecification.class);</span><br><span class="line"><span class="comment">//增加构造函数参数</span></span><br><span class="line">builder.addConstructorArgValue(name);</span><br><span class="line">builder.addConstructorArgValue(configuration);</span><br><span class="line"><span class="comment">//调用BeanDefinitionRegistry.registerBeanDefinition方法动态注册Bean</span></span><br><span class="line">registry.registerBeanDefinition(</span><br><span class="line">name + <span class="string">"."</span> + FeignClientSpecification.class.getSimpleName(),</span><br><span class="line">builder.getBeanDefinition());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里使用spring 动态注册bean的方式,注册了一个FeignClientSpecification的bean。</p><h2 id="FeignClientSpecification-客户端定义"><a href="#FeignClientSpecification-客户端定义" class="headerlink" title="FeignClientSpecification 客户端定义"></a>FeignClientSpecification 客户端定义</h2><p>一个简单的pojo,继承了NamedContextFactory.Specification,两个属性String name 和 Class<?>[] configuration,用于FeignContext命名空间独立配置,后面会用到。</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><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@AllArgsConstructor</span></span><br><span class="line"><span class="meta">@NoArgsConstructor</span></span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FeignClientSpecification</span> <span class="keyword">implements</span> <span class="title">NamedContextFactory</span>.<span class="title">Specification</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String name;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> Class<?>[] configuration;</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="registerFeignClients方法,注册feign客户端"><a href="#registerFeignClients方法,注册feign客户端" class="headerlink" title="registerFeignClients方法,注册feign客户端"></a>registerFeignClients方法,注册feign客户端</h2><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">registerFeignClients</span><span class="params">(AnnotationMetadata metadata,</span></span></span><br><span class="line"><span class="function"><span class="params">BeanDefinitionRegistry registry)</span> </span>{</span><br><span class="line"><span class="comment">//生成一个scanner,扫描注定包下的类</span></span><br><span class="line">ClassPathScanningCandidateComponentProvider scanner = getScanner();</span><br><span class="line">scanner.setResourceLoader(<span class="keyword">this</span>.resourceLoader);</span><br><span class="line"></span><br><span class="line">Set<String> basePackages;</span><br><span class="line"></span><br><span class="line">Map<String, Object> attrs = metadata</span><br><span class="line">.getAnnotationAttributes(EnableFeignClients.class.getName());</span><br><span class="line"><span class="comment">//包含@FeignClient注解的过滤器</span></span><br><span class="line">AnnotationTypeFilter annotationTypeFilter = <span class="keyword">new</span> AnnotationTypeFilter(</span><br><span class="line">FeignClient.class);</span><br><span class="line"><span class="keyword">final</span> Class<?>[] clients = attrs == <span class="keyword">null</span> ? <span class="keyword">null</span></span><br><span class="line">: (Class<?>[]) attrs.get(<span class="string">"clients"</span>);</span><br><span class="line"><span class="keyword">if</span> (clients == <span class="keyword">null</span> || clients.length == <span class="number">0</span>) {</span><br><span class="line"><span class="comment">//@EnableFeignClients没有声明clients,获取basePackages,设置过滤器</span></span><br><span class="line">scanner.addIncludeFilter(annotationTypeFilter);</span><br><span class="line">basePackages = getBasePackages(metadata);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line"><span class="comment">//@EnableFeignClients声明了clients</span></span><br><span class="line"><span class="keyword">final</span> Set<String> clientClasses = <span class="keyword">new</span> HashSet<>();</span><br><span class="line">basePackages = <span class="keyword">new</span> HashSet<>();</span><br><span class="line"><span class="comment">//basePackages为声明的clients所在的包</span></span><br><span class="line"><span class="keyword">for</span> (Class<?> clazz : clients) {</span><br><span class="line">basePackages.add(ClassUtils.getPackageName(clazz));</span><br><span class="line">clientClasses.add(clazz.getCanonicalName());</span><br><span class="line">}</span><br><span class="line"><span class="comment">//增加过滤器,只包含声明的clients</span></span><br><span class="line">AbstractClassTestingTypeFilter filter = <span class="keyword">new</span> AbstractClassTestingTypeFilter() {</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">match</span><span class="params">(ClassMetadata metadata)</span> </span>{</span><br><span class="line">String cleaned = metadata.getClassName().replaceAll(<span class="string">"\\$"</span>, <span class="string">"."</span>);</span><br><span class="line"><span class="keyword">return</span> clientClasses.contains(cleaned);</span><br><span class="line">}</span><br><span class="line">};</span><br><span class="line">scanner.addIncludeFilter(</span><br><span class="line"><span class="keyword">new</span> AllTypeFilter(Arrays.asList(filter, annotationTypeFilter)));</span><br><span class="line">}</span><br><span class="line"><span class="comment">//遍历basePackages</span></span><br><span class="line"><span class="keyword">for</span> (String basePackage : basePackages) {</span><br><span class="line"><span class="comment">//扫描包,根据过滤器找到候选的Bean</span></span><br><span class="line">Set<BeanDefinition> candidateComponents = scanner</span><br><span class="line">.findCandidateComponents(basePackage);</span><br><span class="line"><span class="comment">// 遍历候选的bean</span></span><br><span class="line"><span class="keyword">for</span> (BeanDefinition candidateComponent : candidateComponents) {</span><br><span class="line"><span class="keyword">if</span> (candidateComponent <span class="keyword">instanceof</span> AnnotatedBeanDefinition) {</span><br><span class="line"><span class="comment">// 校验注解是否是注解在接口上</span></span><br><span class="line">AnnotatedBeanDefinition beanDefinition = (AnnotatedBeanDefinition) candidateComponent;</span><br><span class="line">AnnotationMetadata annotationMetadata = beanDefinition.getMetadata();</span><br><span class="line">Assert.isTrue(annotationMetadata.isInterface(),</span><br><span class="line"><span class="string">"@FeignClient can only be specified on an interface"</span>);</span><br><span class="line"><span class="comment">// 获取注解属性</span></span><br><span class="line">Map<String, Object> attributes = annotationMetadata</span><br><span class="line">.getAnnotationAttributes(</span><br><span class="line">FeignClient.class.getCanonicalName());</span><br><span class="line"></span><br><span class="line">String name = getClientName(attributes);</span><br><span class="line"><span class="comment">//注册客户端配置</span></span><br><span class="line">registerClientConfiguration(registry, name,</span><br><span class="line">attributes.get(<span class="string">"configuration"</span>));</span><br><span class="line"><span class="comment">//注册客户端</span></span><br><span class="line">registerFeignClient(registry, annotationMetadata, attributes);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这个方法主要逻辑是扫描注解声明的客户端,调用registerFeignClient方法注册到registry中。这里是一个典型的spring动态注册bean的例子,可以参考这段代码在spring中轻松的实现类路径下class扫描,动态注册bean到spring中。想了解spring类的扫描机制,可以断点到ClassPathScanningCandidateComponentProvider.findCandidateComponents方法中,一步步调试。</p><h2 id="registerFeignClient方法,注册单个客户feign端"><a href="#registerFeignClient方法,注册单个客户feign端" class="headerlink" title="registerFeignClient方法,注册单个客户feign端"></a>registerFeignClient方法,注册单个客户feign端</h2><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">registerFeignClient</span><span class="params">(BeanDefinitionRegistry registry,</span></span></span><br><span class="line"><span class="function"><span class="params">AnnotationMetadata annotationMetadata, Map<String, Object> attributes)</span> </span>{</span><br><span class="line">String className = annotationMetadata.getClassName();</span><br><span class="line"><span class="comment">//构建一个FeignClientFactoryBean的bean工厂定义</span></span><br><span class="line">BeanDefinitionBuilder definition = BeanDefinitionBuilder</span><br><span class="line">.genericBeanDefinition(FeignClientFactoryBean.class);</span><br><span class="line">validate(attributes);</span><br><span class="line"><span class="comment">//根据@FeignClient注解的参数,设置属性</span></span><br><span class="line">definition.addPropertyValue(<span class="string">"url"</span>, getUrl(attributes));</span><br><span class="line">definition.addPropertyValue(<span class="string">"path"</span>, getPath(attributes));</span><br><span class="line">String name = getName(attributes);</span><br><span class="line">definition.addPropertyValue(<span class="string">"name"</span>, name);</span><br><span class="line">definition.addPropertyValue(<span class="string">"type"</span>, className);</span><br><span class="line">definition.addPropertyValue(<span class="string">"decode404"</span>, attributes.get(<span class="string">"decode404"</span>));</span><br><span class="line">definition.addPropertyValue(<span class="string">"fallback"</span>, attributes.get(<span class="string">"fallback"</span>));</span><br><span class="line">definition.addPropertyValue(<span class="string">"fallbackFactory"</span>, attributes.get(<span class="string">"fallbackFactory"</span>));</span><br><span class="line">definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);</span><br><span class="line"></span><br><span class="line">String alias = name + <span class="string">"FeignClient"</span>;</span><br><span class="line">AbstractBeanDefinition beanDefinition = definition.getBeanDefinition();</span><br><span class="line">beanDefinition.setPrimary(<span class="keyword">true</span>);</span><br><span class="line"><span class="comment">//设置qualifier</span></span><br><span class="line">String qualifier = getQualifier(attributes);</span><br><span class="line"><span class="keyword">if</span> (StringUtils.hasText(qualifier)) {</span><br><span class="line">alias = qualifier;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//注册,这里为了简写,新建一个BeanDefinitionHolder,调用BeanDefinitionReaderUtils静态方法注册</span></span><br><span class="line">BeanDefinitionHolder holder = <span class="keyword">new</span> BeanDefinitionHolder(beanDefinition, className,</span><br><span class="line"><span class="keyword">new</span> String[] { alias });</span><br><span class="line">BeanDefinitionReaderUtils.registerBeanDefinition(holder, registry);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>registerFeignClient方法主要是将FeignClientFactoryBean工厂Bean注册到registry中,spring初始化后,会调用FeignClientFactoryBean的getObject方法创建bean注册到spring context中。</p><h2 id="FeignClientFactoryBean-创建feign客户端的工厂"><a href="#FeignClientFactoryBean-创建feign客户端的工厂" class="headerlink" title="FeignClientFactoryBean 创建feign客户端的工厂"></a>FeignClientFactoryBean 创建feign客户端的工厂</h2><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Data</span></span><br><span class="line"><span class="meta">@EqualsAndHashCode</span>(callSuper = <span class="keyword">false</span>)</span><br><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">FeignClientFactoryBean</span> <span class="keyword">implements</span> <span class="title">FactoryBean</span><<span class="title">Object</span>>, <span class="title">InitializingBean</span>,</span></span><br><span class="line"><span class="class"><span class="title">ApplicationContextAware</span> </span>{</span><br><span class="line"><span class="comment">//feign客户端接口类</span></span><br><span class="line"><span class="keyword">private</span> Class<?> type;</span><br><span class="line"><span class="keyword">private</span> String name; </span><br><span class="line"><span class="keyword">private</span> String url;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> String path;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">boolean</span> decode404;</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> ApplicationContext applicationContext;</span><br><span class="line"><span class="comment">//hystrix集成,调用失败的执行方法</span></span><br><span class="line"><span class="keyword">private</span> Class<?> fallback = <span class="keyword">void</span>.class;</span><br><span class="line"><span class="comment">//同上</span></span><br><span class="line"><span class="keyword">private</span> Class<?> fallbackFactory = <span class="keyword">void</span>.class;</span><br><span class="line">。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>FeignClientFactoryBean实现了FactoryBean接口,是一个工厂bean</p><h3 id="FeignClientFactoryBean-getObject方法"><a href="#FeignClientFactoryBean-getObject方法" class="headerlink" title="FeignClientFactoryBean.getObject方法"></a>FeignClientFactoryBean.getObject方法</h3><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">getObject</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"><span class="comment">//FeignContext在FeignAutoConfiguration中自动注册,FeignContext用于客户端配置类独立注册,后面具体分析</span></span><br><span class="line">FeignContext context = applicationContext.getBean(FeignContext.class);</span><br><span class="line"><span class="comment">//创建Feign.Builder</span></span><br><span class="line">Feign.Builder builder = feign(context);</span><br><span class="line"><span class="comment">//如果@FeignClient注解没有设置url参数</span></span><br><span class="line"><span class="keyword">if</span> (!StringUtils.hasText(<span class="keyword">this</span>.url)) {</span><br><span class="line">String url;</span><br><span class="line"><span class="comment">//url为@FeignClient注解的name参数</span></span><br><span class="line"><span class="keyword">if</span> (!<span class="keyword">this</span>.name.startsWith(<span class="string">"http"</span>)) {</span><br><span class="line">url = <span class="string">"http://"</span> + <span class="keyword">this</span>.name;</span><br><span class="line">}</span><br><span class="line"><span class="keyword">else</span> {</span><br><span class="line">url = <span class="keyword">this</span>.name;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//加上path</span></span><br><span class="line">url += cleanPath();</span><br><span class="line"><span class="comment">//返回loadBlance客户端,也就是ribbon+eureka的客户端</span></span><br><span class="line"><span class="keyword">return</span> loadBalance(builder, context, <span class="keyword">new</span> HardCodedTarget<>(<span class="keyword">this</span>.type,</span><br><span class="line"><span class="keyword">this</span>.name, url));</span><br><span class="line">}</span><br><span class="line"><span class="comment">//@FeignClient设置了url参数,不做负载均衡</span></span><br><span class="line"><span class="keyword">if</span> (StringUtils.hasText(<span class="keyword">this</span>.url) && !<span class="keyword">this</span>.url.startsWith(<span class="string">"http"</span>)) {</span><br><span class="line"><span class="keyword">this</span>.url = <span class="string">"http://"</span> + <span class="keyword">this</span>.url;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//加上path</span></span><br><span class="line">String url = <span class="keyword">this</span>.url + cleanPath();</span><br><span class="line"><span class="comment">//从FeignContext中获取client</span></span><br><span class="line">Client client = getOptional(context, Client.class);</span><br><span class="line"><span class="keyword">if</span> (client != <span class="keyword">null</span>) {</span><br><span class="line"><span class="keyword">if</span> (client <span class="keyword">instanceof</span> LoadBalancerFeignClient) {</span><br><span class="line"><span class="comment">// 有url参数,不做负载均衡,但是客户端是ribbon,或者实际的客户端</span></span><br><span class="line">client = ((LoadBalancerFeignClient)client).getDelegate();</span><br><span class="line">}</span><br><span class="line">builder.client(client);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//从FeignContext中获取Targeter</span></span><br><span class="line">Targeter targeter = get(context, Targeter.class);</span><br><span class="line"><span class="comment">//生成客户端代理</span></span><br><span class="line"><span class="keyword">return</span> targeter.target(<span class="keyword">this</span>, builder, context, <span class="keyword">new</span> HardCodedTarget<>(</span><br><span class="line"><span class="keyword">this</span>.type, <span class="keyword">this</span>.name, url));</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这段代码有个比较重要的逻辑,如果在@FeignClient注解中设置了url参数,就不走Ribbon,直接url调用,否则通过Ribbon调用,实现客户端负载均衡。</p><p>可以看到,生成Feign客户端所需要的各种配置对象,都是通过FeignContex中获取的。</p><h3 id="FeignContext-隔离配置"><a href="#FeignContext-隔离配置" class="headerlink" title="FeignContext 隔离配置"></a>FeignContext 隔离配置</h3><p>在@FeignClient注解参数configuration,指定的类是Spring的Configuration Bean,里面方法上加@Bean注解实现Bean的注入,可以指定feign客户端的各种配置,包括Encoder/Decoder/Contract/Feign.Builder等。不同的客户端指定不同配置类,就需要对配置类进行隔离,FeignContext就是用于隔离配置的。</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><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FeignContext</span> <span class="keyword">extends</span> <span class="title">NamedContextFactory</span><<span class="title">FeignClientSpecification</span>> </span>{</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">FeignContext</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">super</span>(FeignClientsConfiguration.class, <span class="string">"feign"</span>, <span class="string">"feign.client.name"</span>);</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>FeignContext继承NamedContextFactory,空参数构造函数指定FeignClientsConfiguration类为默认配置。<br>NamedContextFactory实现接口ApplicationContextAware,注入ApplicationContextAware作为parent:<br><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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">abstract</span> <span class="class"><span class="keyword">class</span> <span class="title">NamedContextFactory</span><<span class="title">C</span> <span class="keyword">extends</span> <span class="title">NamedContextFactory</span>.<span class="title">Specification</span>></span></span><br><span class="line"><span class="class"><span class="keyword">implements</span> <span class="title">DisposableBean</span>, <span class="title">ApplicationContextAware</span> </span>{</span><br><span class="line"><span class="comment">//命名空间对应的Spring Context</span></span><br><span class="line"><span class="keyword">private</span> Map<String, AnnotationConfigApplicationContext> contexts = <span class="keyword">new</span> ConcurrentHashMap<>();</span><br><span class="line"><span class="comment">//不同命名空间的定义</span></span><br><span class="line"><span class="keyword">private</span> Map<String, C> configurations = <span class="keyword">new</span> ConcurrentHashMap<>();</span><br><span class="line"><span class="comment">//父ApplicationContext,通过ApplicationContextAware接口注入</span></span><br><span class="line"><span class="keyword">private</span> ApplicationContext parent;</span><br><span class="line"><span class="comment">//默认配置类</span></span><br><span class="line"><span class="keyword">private</span> Class<?> defaultConfigType;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> String propertySourceName;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> String propertyName;</span><br><span class="line">。。。</span><br><span class="line"><span class="comment">//设置配置,在FeignAutoConfiguration中将Spring Context中的所有FeignClientSpecification设置进来,如果@EnableFeignClients有设置参数defaultConfiguration也会加进来,前面已经分析在registerDefaultConfiguration方法中注册的FeignClientSpecification Bean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setConfigurations</span><span class="params">(List<C> configurations)</span> </span>{</span><br><span class="line"><span class="keyword">for</span> (C client : configurations) {</span><br><span class="line"><span class="keyword">this</span>.configurations.put(client.getName(), client);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//获取指定命名空间的ApplicationContext,先从缓存中获取,没有就创建</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> AnnotationConfigApplicationContext <span class="title">getContext</span><span class="params">(String name)</span> </span>{</span><br><span class="line"><span class="keyword">if</span> (!<span class="keyword">this</span>.contexts.containsKey(name)) {</span><br><span class="line"><span class="keyword">synchronized</span> (<span class="keyword">this</span>.contexts) {</span><br><span class="line"><span class="keyword">if</span> (!<span class="keyword">this</span>.contexts.containsKey(name)) {</span><br><span class="line"><span class="keyword">this</span>.contexts.put(name, createContext(name));</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">this</span>.contexts.get(name);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//创建ApplicationContext</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> AnnotationConfigApplicationContext <span class="title">createContext</span><span class="params">(String name)</span> </span>{</span><br><span class="line"><span class="comment">//新建AnnotationConfigApplicationContext</span></span><br><span class="line">AnnotationConfigApplicationContext context = <span class="keyword">new</span> AnnotationConfigApplicationContext();</span><br><span class="line"><span class="comment">//根据name在configurations找到所有的配置类,注册到context总</span></span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">this</span>.configurations.containsKey(name)) {</span><br><span class="line"><span class="keyword">for</span> (Class<?> configuration : <span class="keyword">this</span>.configurations.get(name)</span><br><span class="line">.getConfiguration()) {</span><br><span class="line">context.register(configuration);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="comment">//将default.开头的默认默认也注册到Context中</span></span><br><span class="line"><span class="keyword">for</span> (Map.Entry<String, C> entry : <span class="keyword">this</span>.configurations.entrySet()) {</span><br><span class="line"><span class="keyword">if</span> (entry.getKey().startsWith(<span class="string">"default."</span>)) {</span><br><span class="line"><span class="keyword">for</span> (Class<?> configuration : entry.getValue().getConfiguration()) {</span><br><span class="line">context.register(configuration);</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="comment">//注册一些需要的bean</span></span><br><span class="line">context.register(PropertyPlaceholderAutoConfiguration.class,</span><br><span class="line"><span class="keyword">this</span>.defaultConfigType);</span><br><span class="line">context.getEnvironment().getPropertySources().addFirst(<span class="keyword">new</span> MapPropertySource(</span><br><span class="line"><span class="keyword">this</span>.propertySourceName,</span><br><span class="line">Collections.<String, Object> singletonMap(<span class="keyword">this</span>.propertyName, name)));</span><br><span class="line"><span class="keyword">if</span> (<span class="keyword">this</span>.parent != <span class="keyword">null</span>) {</span><br><span class="line"><span class="comment">// 设置parent</span></span><br><span class="line">context.setParent(<span class="keyword">this</span>.parent);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//刷新,完成bean生成</span></span><br><span class="line">context.refresh();</span><br><span class="line"><span class="keyword">return</span> context;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//从命名空间中获取指定类型的Bean</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">getInstance</span><span class="params">(String name, Class<T> type)</span> </span>{</span><br><span class="line">AnnotationConfigApplicationContext context = getContext(name);</span><br><span class="line"><span class="keyword">if</span> (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,</span><br><span class="line">type).length > <span class="number">0</span>) {</span><br><span class="line"><span class="keyword">return</span> context.getBean(type);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//从命名空间中获取指定类型的Bean</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">Map<String, T> <span class="title">getInstances</span><span class="params">(String name, Class<T> type)</span> </span>{</span><br><span class="line">AnnotationConfigApplicationContext context = getContext(name);</span><br><span class="line"><span class="keyword">if</span> (BeanFactoryUtils.beanNamesForTypeIncludingAncestors(context,</span><br><span class="line">type).length > <span class="number">0</span>) {</span><br><span class="line"><span class="keyword">return</span> BeanFactoryUtils.beansOfTypeIncludingAncestors(context, type);</span><br><span class="line">}</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>关键的方法是createContext,为每个命名空间独立创建ApplicationContext,设置parent为外部传入的Context,这样就可以共用外部的Context中的Bean,又有各种独立的配置Bean,熟悉springMVC的同学应该知道,springMVC中创建的WebApplicatonContext里面也有个parent,原理跟这个类似。</p><p>从FeignContext中获取Bean,需要传入命名空间,根据命名空间找到缓存中的ApplicationContext,先从自己注册的Bean中获取bean,没有获取到再从到parent中获取。</p><h3 id="创建Feign-Builder"><a href="#创建Feign-Builder" class="headerlink" title="创建Feign.Builder"></a>创建Feign.Builder</h3><p>了解了FeignContext的原理,我们再来看feign最重要的构建类创建过程<br><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><span class="line"><span class="keyword">protected</span> Feign.<span class="function">Builder <span class="title">feign</span><span class="params">(FeignContext context)</span> </span>{</span><br><span class="line">。。。</span><br><span class="line"><span class="comment">//从FeignContext中获取注册的Feign.Builder bean,设置Encoder/Decoder/Contract</span></span><br><span class="line">Feign.Builder builder = get(context, Feign.Builder.class)</span><br><span class="line">.logger(logger)</span><br><span class="line">.encoder(get(context, Encoder.class))</span><br><span class="line">.decoder(get(context, Decoder.class))</span><br><span class="line">.contract(get(context, Contract.class));</span><br><span class="line">。。。</span><br><span class="line"><span class="comment">//设置feign其他参数,都从FeignContext中获取</span></span><br><span class="line">Retryer retryer = getOptional(context, Retryer.class);</span><br><span class="line"><span class="keyword">if</span> (retryer != <span class="keyword">null</span>) {</span><br><span class="line">builder.retryer(retryer);</span><br><span class="line">}</span><br><span class="line">ErrorDecoder errorDecoder = getOptional(context, ErrorDecoder.class);</span><br><span class="line"><span class="keyword">if</span> (errorDecoder != <span class="keyword">null</span>) {</span><br><span class="line">builder.errorDecoder(errorDecoder);</span><br><span class="line">}</span><br><span class="line">Request.Options options = getOptional(context, Request.Options.class);</span><br><span class="line"><span class="keyword">if</span> (options != <span class="keyword">null</span>) {</span><br><span class="line">builder.options(options);</span><br><span class="line">}</span><br><span class="line">Map<String, RequestInterceptor> requestInterceptors = context.getInstances(</span><br><span class="line"><span class="keyword">this</span>.name, RequestInterceptor.class);</span><br><span class="line"><span class="keyword">if</span> (requestInterceptors != <span class="keyword">null</span>) {</span><br><span class="line">builder.requestInterceptors(requestInterceptors.values());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (decode404) {</span><br><span class="line">builder.decode404();</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> builder;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里设置了Feign.Builder所必须的参数Encoder/Decoder/Contract,其他参数都是可选的。这三个必须的参数从哪里来的呢?答案是在FeignContext的构造器中,传入了默认的配置FeignClientsConfiguration,这个配置类里面初始化了这三个参数。</p><h3 id="FeignClientsConfiguration-客户端默认配置"><a href="#FeignClientsConfiguration-客户端默认配置" class="headerlink" title="FeignClientsConfiguration 客户端默认配置"></a>FeignClientsConfiguration 客户端默认配置</h3><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">FeignClientsConfiguration</span> </span>{</span><br><span class="line"><span class="comment">//注入springMVC的HttpMessageConverters</span></span><br><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> ObjectFactory<HttpMessageConverters> messageConverters;</span><br><span class="line"><span class="comment">//注解参数处理器,处理SpringMVC注解,生成http元数据</span></span><br><span class="line"><span class="meta">@Autowired</span>(required = <span class="keyword">false</span>)</span><br><span class="line"><span class="keyword">private</span> List<AnnotatedParameterProcessor> parameterProcessors = <span class="keyword">new</span> ArrayList<>();</span><br><span class="line">。。。</span><br><span class="line"><span class="comment">//Decoder bean,默认通过HttpMessageConverters进行处理</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Decoder <span class="title">feignDecoder</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> ResponseEntityDecoder(<span class="keyword">new</span> SpringDecoder(<span class="keyword">this</span>.messageConverters));</span><br><span class="line">}</span><br><span class="line"><span class="comment">//Encoder bean,默认通过HttpMessageConverters进行处理</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Encoder <span class="title">feignEncoder</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> SpringEncoder(<span class="keyword">this</span>.messageConverters);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//Contract bean,通过SpringMvcContract进行处理接口</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Contract <span class="title">feignContract</span><span class="params">(ConversionService feignConversionService)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> <span class="keyword">new</span> SpringMvcContract(<span class="keyword">this</span>.parameterProcessors, feignConversionService);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//hystrix自动注入</span></span><br><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConditionalOnClass</span>({ HystrixCommand.class, HystrixFeign.class })</span><br><span class="line"><span class="keyword">protected</span> <span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">HystrixFeignConfiguration</span> </span>{</span><br><span class="line"><span class="comment">//HystrixFeign的builder,全局关掉Hystrix配置feign.hystrix.enabled=false</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@Scope</span>(<span class="string">"prototype"</span>)</span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="meta">@ConditionalOnProperty</span>(name = <span class="string">"feign.hystrix.enabled"</span>, matchIfMissing = <span class="keyword">true</span>)</span><br><span class="line"><span class="keyword">public</span> Feign.<span class="function">Builder <span class="title">feignHystrixBuilder</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> HystrixFeign.builder();</span><br><span class="line">}</span><br><span class="line">}</span><br><span class="line"><span class="comment">//默认不重试</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Retryer <span class="title">feignRetryer</span><span class="params">()</span> </span>{</span><br><span class="line"><span class="keyword">return</span> Retryer.NEVER_RETRY;</span><br><span class="line">}</span><br><span class="line"><span class="comment">//默认的builder</span></span><br><span class="line"><span class="meta">@Bean</span></span><br><span class="line"><span class="meta">@Scope</span>(<span class="string">"prototype"</span>)</span><br><span class="line"><span class="meta">@ConditionalOnMissingBean</span></span><br><span class="line"><span class="keyword">public</span> Feign.<span class="function">Builder <span class="title">feignBuilder</span><span class="params">(Retryer retryer)</span> </span>{</span><br><span class="line"><span class="keyword">return</span> Feign.builder().retryer(retryer);</span><br><span class="line">}</span><br><span class="line">。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到,feign需要的decoder/enoder通过适配器共用springMVC中的HttpMessageConverters引入。</p><p>feign有自己的注解体系,这里通过SpringMvcContract适配了springMVC的注解体系。</p><h3 id="SpringMvcContract-适配feign注解体系"><a href="#SpringMvcContract-适配feign注解体系" class="headerlink" title="SpringMvcContract 适配feign注解体系"></a>SpringMvcContract 适配feign注解体系</h3><p>SpringMvcContract继承了feign的类Contract.BaseContract,作用是解析接口方法上的注解和方法参数,生成MethodMetadata用于接口方法调用过程中组装http请求。</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringMvcContract</span> <span class="keyword">extends</span> <span class="title">Contract</span>.<span class="title">BaseContract</span></span></span><br><span class="line"><span class="class"><span class="keyword">implements</span> <span class="title">ResourceLoaderAware</span> </span>{</span><br><span class="line">。。。</span><br><span class="line"></span><br><span class="line"><span class="comment">//处理Class上的注解</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">processAnnotationOnClass</span><span class="params">(MethodMetadata data, Class<?> clz)</span> </span>{</span><br><span class="line">。。。</span><br><span class="line">}</span><br><span class="line"><span class="comment">//处理方法</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> MethodMetadata <span class="title">parseAndValidateMetadata</span><span class="params">(Class<?> targetType, Method method)</span> </span>{</span><br><span class="line">。。。</span><br><span class="line">}</span><br><span class="line"><span class="comment">//处理方法上的注解</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">processAnnotationOnMethod</span><span class="params">(MethodMetadata data,</span></span></span><br><span class="line"><span class="function"><span class="params">Annotation methodAnnotation, Method method)</span> </span>{</span><br><span class="line"> 。。。</span><br><span class="line">}</span><br><span class="line"><span class="comment">//处理参数上的注解</span></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">processAnnotationsOnParameter</span><span class="params">(MethodMetadata data,</span></span></span><br><span class="line"><span class="function"><span class="params"> Annotation[] annotations, <span class="keyword">int</span> paramIndex)</span> </span>{</span><br><span class="line"> 。。。</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>几个覆盖方法分别是处理类上的注解,处理方法,处理方法上的注解,处理方法参数注解,最终生成完整的MethodMetadata。feign自己提供的Contract和扩展javax.ws.rx的Contract原理都是类似的。</p><h3 id="Targeter-生成接口动态代理"><a href="#Targeter-生成接口动态代理" class="headerlink" title="Targeter 生成接口动态代理"></a>Targeter 生成接口动态代理</h3><p>Feign.Builder生成后,就要用Target生成feign客户端的动态代理,这里FeignClientFactoryBean中使用Targeter,Targeter有两个实现类,分别是HystrixTargeter和DefaultTargeter,DefaultTargeter很简单,直接调用HardCodedTarget生成动态代理,HystrixTargeter源码如下:</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><span class="line"><span class="class"><span class="keyword">class</span> <span class="title">HystrixTargeter</span> <span class="keyword">implements</span> <span class="title">Targeter</span> </span>{</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">target</span><span class="params">(FeignClientFactoryBean factory, Feign.Builder feign, FeignContext context,</span></span></span><br><span class="line"><span class="function"><span class="params">Target.HardCodedTarget<T> target)</span> </span>{</span><br><span class="line"><span class="comment">//如果不是HystrixFeign.Builder,直接调用target生成代理</span></span><br><span class="line"><span class="keyword">if</span> (!(feign <span class="keyword">instanceof</span> feign.hystrix.HystrixFeign.Builder)) {</span><br><span class="line"><span class="keyword">return</span> feign.target(target);</span><br><span class="line">}</span><br><span class="line"><span class="comment">//找到fallback或者fallbackFactory,设置到hystrix中</span></span><br><span class="line">feign.hystrix.HystrixFeign.Builder builder = (feign.hystrix.HystrixFeign.Builder) feign;</span><br><span class="line">Class<?> fallback = factory.getFallback();</span><br><span class="line"><span class="keyword">if</span> (fallback != <span class="keyword">void</span>.class) {</span><br><span class="line"><span class="keyword">return</span> targetWithFallback(factory.getName(), context, target, builder, fallback);</span><br><span class="line">}</span><br><span class="line">Class<?> fallbackFactory = factory.getFallbackFactory();</span><br><span class="line"><span class="keyword">if</span> (fallbackFactory != <span class="keyword">void</span>.class) {</span><br><span class="line"><span class="keyword">return</span> targetWithFallbackFactory(factory.getName(), context, target, builder, fallbackFactory);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">return</span> feign.target(target);</span><br><span class="line">}</span><br><span class="line"> 。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>到这里,接口的动态代理就生成了,然后回到FeignClientFactoryBean工厂bean中,会将动态代理注入到SpringContext,在使用的地方,就可以通过@Autowire方式注入了。</p><h2 id="loadBalance方法,客户端负载均衡"><a href="#loadBalance方法,客户端负载均衡" class="headerlink" title="loadBalance方法,客户端负载均衡"></a>loadBalance方法,客户端负载均衡</h2><p>如果@FeignClient注解中没有配置url参数,将会通过loadBalance方法生成Ribbon的动态代理:</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><span class="line"><span class="keyword">protected</span> <T> <span class="function">T <span class="title">loadBalance</span><span class="params">(Feign.Builder builder, FeignContext context,</span></span></span><br><span class="line"><span class="function"><span class="params">HardCodedTarget<T> target)</span> </span>{</span><br><span class="line"><span class="comment">//这里获取到的Client是LoadBalancerFeignClient</span></span><br><span class="line">Client client = getOptional(context, Client.class);</span><br><span class="line"><span class="keyword">if</span> (client != <span class="keyword">null</span>) {</span><br><span class="line">builder.client(client);</span><br><span class="line">Targeter targeter = get(context, Targeter.class);</span><br><span class="line"><span class="keyword">return</span> targeter.target(<span class="keyword">this</span>, builder, context, target);</span><br><span class="line">}</span><br><span class="line">。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>LoadBalancerFeignClient在FeignRibbonClientAutoConfiguration中自动配置的Bean</p><h3 id="LoadBalancerFeignClient-负载均衡客户端"><a href="#LoadBalancerFeignClient-负载均衡客户端" class="headerlink" title="LoadBalancerFeignClient 负载均衡客户端"></a>LoadBalancerFeignClient 负载均衡客户端</h3><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LoadBalancerFeignClient</span> <span class="keyword">implements</span> <span class="title">Client</span> </span>{</span><br><span class="line">。。。</span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Response <span class="title">execute</span><span class="params">(Request request, Request.Options options)</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"><span class="keyword">try</span> {</span><br><span class="line"><span class="comment">//获取URI</span></span><br><span class="line">URI asUri = URI.create(request.url());</span><br><span class="line"><span class="comment">//获取客户端的名称</span></span><br><span class="line">String clientName = asUri.getHost();</span><br><span class="line">URI uriWithoutHost = cleanUrl(request.url(), clientName);</span><br><span class="line"><span class="comment">//创建RibbonRequest</span></span><br><span class="line">FeignLoadBalancer.RibbonRequest ribbonRequest = <span class="keyword">new</span> FeignLoadBalancer.RibbonRequest(</span><br><span class="line"><span class="keyword">this</span>.delegate, request, uriWithoutHost);</span><br><span class="line"><span class="comment">//配置</span></span><br><span class="line">IClientConfig requestConfig = getClientConfig(options, clientName);</span><br><span class="line"><span class="comment">//获取FeignLoadBalancer,发请求,转换Response</span></span><br><span class="line"><span class="keyword">return</span> lbClient(clientName).executeWithLoadBalancer(ribbonRequest,</span><br><span class="line">requestConfig).toResponse();</span><br><span class="line">} <span class="keyword">catch</span> (ClientException e) {</span><br><span class="line">。。。</span><br><span class="line">}</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>代码逻辑也比较简单,就是是配到Ribbon客户端上调用。Ribbon的相关使用和原理就不在本文中描述。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>feign本身是一款优秀的开源组件,spring cloud feign又非常巧妙的将feign集成到spring boot中。<br>本文通过对spring cloud feign源代码的解读,详细的分析了feign集成到spring boot中的原理,使我们更加全面的了解到feign的使用。</p><p>spring cloud feign也是一个很好的学习spring boot的例子,从中我们可以学习到:</p><ul><li>spring boot注解声明注入bean</li><li>spring类扫描机制</li><li>spring接口动态注册bean</li><li>spring命名空间隔离ApplicationContext</li></ul>]]></content>
<summary type="html">
<h1 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h1><p><a href="https://github.com/OpenFeign/feign" target="_blank" rel="noopener">feign</a>是一个声明试的HTTP客户端,spring-cloud-openfeign将feign集成到spring boot中,在接口上通过注解声明Rest协议,将http调用转换为接口方法的调用,使得客户端调用http服务更加简单。</p>
<p>当前spring cloud最新稳定版本是Edgware,feign在其集成的<a href="https://github.com/spring-cloud/spring-cloud-netflix" target="_blank" rel="noopener">spring-cloud-netflix</a> 1.4.0.RELEASE版本中。</p>
<blockquote>
<p>spring cloud下一个版本是Finchley,将会单独集成<a href="https://github.com/spring-cloud/spring-cloud-openfeign" target="_blank" rel="noopener">spring-cloud-openfeign</a></p>
</blockquote>
</summary>
<category term="spring" scheme="http://along101.site/categories/spring/"/>
<category term="rest" scheme="http://along101.site/tags/rest/"/>
<category term="feign" scheme="http://along101.site/tags/feign/"/>
<category term="spring-cloud" scheme="http://along101.site/tags/spring-cloud/"/>
</entry>
<entry>
<title>Feign使用教程,翻译版本</title>
<link href="http://along101.site/2018/06/14/feign-user-guide/"/>
<id>http://along101.site/2018/06/14/feign-user-guide/</id>
<published>2018-06-14T12:03:13.000Z</published>
<updated>2018-07-20T03:10:04.405Z</updated>
<content type="html"><![CDATA[<p>英文原文请参考:<a href="https://github.com/OpenFeign/feign" target="_blank" rel="noopener">https://github.com/OpenFeign/feign</a><br>翻译参考:<a href="https://blog.csdn.net/u010862794/article/details/73649616" target="_blank" rel="noopener">https://blog.csdn.net/u010862794/article/details/73649616</a></p><h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Feign是一款java的Restful客户端组件,Feign使得 Java HTTP 客户端编写更方便。Feign 灵感来源于<a href="https://github.com/square/retrofit" target="_blank" rel="noopener">Retrofit</a>, <a href="https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html" target="_blank" rel="noopener">JAXRS-2.0</a>和<a href="http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html" target="_blank" rel="noopener">WebSocket</a>。Feign 最初是为了降低统一绑定<a href="https://github.com/Netflix/Denominator" target="_blank" rel="noopener">Denominator</a> 到 HTTP API 的复杂度,不区分是否支持 <a href="http://www.slideshare.net/adrianfcole/99problems" target="_blank" rel="noopener">ReSTfulness</a>。</p><a id="more"></a><h3 id="为什么选择Feign而不是其他"><a href="#为什么选择Feign而不是其他" class="headerlink" title="为什么选择Feign而不是其他"></a>为什么选择Feign而不是其他</h3><p>你可以使用 Jersey 和 CXF 这些来写一个 Rest 或 SOAP 服务的java客服端。你也可以直接使用 Apache HttpClient 来实现。但是 Feign 的目的是尽量的减少资源和代码来实现和 HTTP API 的连接。通过自定义的编码解码器以及错误处理,你可以编写任何基于文本的 HTTP API。</p><h3 id="Feign工作机制"><a href="#Feign工作机制" class="headerlink" title="Feign工作机制"></a>Feign工作机制</h3><p>Feign 通过注解注入一个模板化请求进行工作。只需在发送之前关闭它,参数就可以被直接的运用到模板中。然而这也限制了 Feign,只支持文本形式的API,它在响应请求等方面极大的简化了系统。同时,它也是十分容易进行单元测试的。</p><h3 id="基本用法"><a href="#基本用法" class="headerlink" title="基本用法"></a>基本用法</h3><p>基本的使用如下所示,一个对于<a href="https://github.com/square/retrofit/blob/master/samples/src/main/java/com/example/retrofit/SimpleService.java" target="_blank" rel="noopener">canonical Retrofit sample</a>的适配。</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><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">GitHub</span> </span>{</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /repos/{owner}/{repo}/contributors"</span>)</span><br><span class="line"> <span class="function">List<Contributor> <span class="title">contributors</span><span class="params">(@Param(<span class="string">"owner"</span>)</span> String owner, @<span class="title">Param</span><span class="params">(<span class="string">"repo"</span>)</span> String repo)</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Contributor</span> </span>{</span><br><span class="line"> String login;</span><br><span class="line"> <span class="keyword">int</span> contributions;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String... args)</span> </span>{</span><br><span class="line"> GitHub github = Feign.builder()</span><br><span class="line"> .decoder(<span class="keyword">new</span> GsonDecoder())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Fetch and print a list of the contributors to this library.</span></span><br><span class="line"> List<Contributor> contributors = github.contributors(<span class="string">"OpenFeign"</span>, <span class="string">"feign"</span>);</span><br><span class="line"> <span class="keyword">for</span> (Contributor contributor : contributors) {</span><br><span class="line"> System.out.println(contributor.login + <span class="string">" ("</span> + contributor.contributions + <span class="string">")"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h3 id="自定义"><a href="#自定义" class="headerlink" title="自定义"></a>自定义</h3><p>Feign 有许多可以自定义的方面。举个简单的例子,你可以使用 <code>Feign.builder()</code> 来构造一个拥有你自己组件的API接口。如下:<br><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><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">Bank</span> </span>{</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"POST /account/{id}"</span>)</span><br><span class="line"> <span class="function">Account <span class="title">getAccountInfo</span><span class="params">(@Param(<span class="string">"id"</span>)</span> String id)</span>;</span><br><span class="line">}</span><br><span class="line">...</span><br><span class="line"><span class="comment">// AccountDecoder 是自己实现的一个Decoder</span></span><br><span class="line">Bank bank = Feign.builder().decoder(<span class="keyword">new</span> AccountDecoder()).target(Bank.class, <span class="string">"https://api.examplebank.com"</span>);</span><br></pre></td></tr></table></figure></p><h3 id="多种接口"><a href="#多种接口" class="headerlink" title="多种接口"></a>多种接口</h3><p>Feign可以提供多种API接口,这些接口都被定义为 <code>Target<T></code> (默认的实现是 <code>HardCodedTarget<T></code>), 它允许在执行请求前动态发现和装饰该请求。</p><p>举个例子,下面的这个模式允许使用当前url和身份验证token来装饰每个发往身份验证中心服务的请求。<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">CloudDNS cloudDNS = Feign.builder().target(<span class="keyword">new</span> CloudIdentityTarget<CloudDNS>(user, apiKey));</span><br></pre></td></tr></table></figure></p><h3 id="示例"><a href="#示例" class="headerlink" title="示例"></a>示例</h3><p>Feign 包含了 <a href="./example-github">GitHub</a> 和 <a href="./example-wikipedia">Wikipedia</a> 客户端的实现样例.相似的项目也同样在实践中运用了Feign。尤其是它的示例后台程序<a href="https://github.com/Netflix/denominator/tree/master/example-daemon" target="_blank" rel="noopener">example daemon</a>。</p><h3 id="Feign集成模块"><a href="#Feign集成模块" class="headerlink" title="Feign集成模块"></a>Feign集成模块</h3><p>Feign 可以和其他的开源工具集成工作。你可以将这些开源工具集成到 Feign 中来。目前已经有的一些模块如下:</p><h3 id="Gson"><a href="#Gson" class="headerlink" title="Gson"></a>Gson</h3><p>Gson 包含了一个编码器和一个解码器,这个可以被用于JSON格式的API。<br>添加 <code>GsonEncoder</code> 以及 <code>GsonDecoder</code> 到你的 <code>Feign.Builder</code> 中, 如下:</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></pre></td><td class="code"><pre><span class="line">GsonCodec codec = <span class="keyword">new</span> GsonCodec();</span><br><span class="line">GitHub github = Feign.builder()</span><br><span class="line"> .encoder(<span class="keyword">new</span> GsonEncoder())</span><br><span class="line"> .decoder(<span class="keyword">new</span> GsonDecoder())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br></pre></td></tr></table></figure><h3 id="Jackson"><a href="#Jackson" class="headerlink" title="Jackson"></a>Jackson</h3><p>Jackson 包含了一个编码器和一个解码器,这个可以被用于JSON格式的API。<br>添加 <code>JacksonEncoder</code> 以及 <code>JacksonDecoder</code> 到你的 <code>Feign.Builder</code> 中, 如下:<br><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><span class="line">GitHub github = Feign.builder()</span><br><span class="line"> .encoder(<span class="keyword">new</span> JacksonEncoder())</span><br><span class="line"> .decoder(<span class="keyword">new</span> JacksonDecoder())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br></pre></td></tr></table></figure></p><p>Maven依赖:</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></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.feign<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>feign-jackson<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>8.18.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><h3 id="Sax"><a href="#Sax" class="headerlink" title="Sax"></a>Sax</h3><p>SaxDecoder 用于解析XML,并兼容普通JVM和Android。下面是一个配置sax来解析响应的例子:<br><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></pre></td><td class="code"><pre><span class="line">api = Feign.builder()</span><br><span class="line"> .decoder(SAXDecoder.builder()</span><br><span class="line"> .registerContentHandler(UserIdHandler.class)</span><br><span class="line"> .build())</span><br><span class="line"> .target(Api.class, <span class="string">"https://apihost"</span>);</span><br></pre></td></tr></table></figure></p><p>Maven依赖:<br><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><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.feign<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>feign-sax<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>8.18.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></p><h3 id="JAXB"><a href="#JAXB" class="headerlink" title="JAXB"></a>JAXB</h3><p>JAXB 包含了一个编码器和一个解码器,这个可以被用于XML格式的API。<br>添加 <code>JAXBEncoder</code> 以及 <code>JAXBDecoder</code> 到你的 <code>Feign.Builder</code> 中, 如下:<br><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><span class="line">api = Feign.builder()</span><br><span class="line"> .encoder(<span class="keyword">new</span> JAXBEncoder())</span><br><span class="line"> .decoder(<span class="keyword">new</span> JAXBDecoder())</span><br><span class="line"> .target(Api.class, <span class="string">"https://apihost"</span>);</span><br></pre></td></tr></table></figure></p><p>Maven依赖:<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.feign<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>feign-jaxb<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>8.18.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></p><h3 id="JAX-RS"><a href="#JAX-RS" class="headerlink" title="JAX-RS"></a>JAX-RS</h3><p>JAXRSContract 使用 JAX-RS 规范重写覆盖了默认的注解处理。下面是一个使用 JAX-RS 的例子:</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><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">GitHub</span> </span>{</span><br><span class="line"> <span class="meta">@GET</span> <span class="meta">@Path</span>(<span class="string">"/repos/{owner}/{repo}/contributors"</span>)</span><br><span class="line"> <span class="function">List<Contributor> <span class="title">contributors</span><span class="params">(@PathParam(<span class="string">"owner"</span>)</span> String owner, @<span class="title">PathParam</span><span class="params">(<span class="string">"repo"</span>)</span> String repo)</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">GitHub github = Feign.builder()</span><br><span class="line"> .contract(<span class="keyword">new</span> JAXRSContract())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br></pre></td></tr></table></figure><p>Maven依赖:</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></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.feign<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>feign-jaxrs<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>8.18.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure><h3 id="OkHttp"><a href="#OkHttp" class="headerlink" title="OkHttp"></a>OkHttp</h3><p>OkHttpClient 使用 OkHttp 来发送 Feign 的请求,OkHttp 支持 SPDY (SPDY是Google开发的基于TCP的传输层协议,用以最小化网络延迟,提升网络速度,优化用户的网络使用体验),并有更好的控制http请求。</p><p>要让 Feign 使用 OkHttp ,你需要将 OkHttp 加入到你的环境变量中区,然后配置 Feign 使用 <code>OkHttpClient</code>,如下:</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></pre></td><td class="code"><pre><span class="line">GitHub github = Feign.builder()</span><br><span class="line"> .client(<span class="keyword">new</span> OkHttpClient())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br></pre></td></tr></table></figure><p>Maven依赖:<br><figure class="highlight plain"><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><span class="line"><!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson --></span><br><span class="line"><dependency></span><br><span class="line"> <groupId>com.netflix.feign</groupId></span><br><span class="line"> <artifactId>feign-okhttp</artifactId></span><br><span class="line"> <version>8.18.0</version></span><br><span class="line"></dependency></span><br></pre></td></tr></table></figure></p><h3 id="Ribbon"><a href="#Ribbon" class="headerlink" title="Ribbon"></a>Ribbon</h3><p>RibbonClient 重写了 Feign 客户端的对URL的处理,其添加了 智能路由以及一些其他由Ribbon提供的弹性功能。<br>集成Ribbon需要你将ribbon的客户端名称当做url的host部分来传递,如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// myAppProd是你的ribbon client name</span></span><br><span class="line">MyService api = Feign.builder().client(RibbonClient.create()).target(MyService.class, <span class="string">"https://myAppProd"</span>);</span><br></pre></td></tr></table></figure></p><p>Maven依赖:<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.feign<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>feign-ribbon<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>8.18.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></p><h3 id="Hystrix"><a href="#Hystrix" class="headerlink" title="Hystrix"></a>Hystrix</h3><p>HystrixFeign 配置了 Hystrix 提供的熔断机制。<br>要在 Feign 中使用 Hystrix ,你需要添加Hystrix模块到你的环境变量,然后使用 <code>HystrixFeign</code> 来构造你的API:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">MyService api = HystrixFeign.builder().target(MyService.class, <span class="string">"https://myAppProd"</span>);</span><br></pre></td></tr></table></figure></p><p>Maven依赖:<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.feign<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>feign-hystrix<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>8.18.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></p><h3 id="SLF4J"><a href="#SLF4J" class="headerlink" title="SLF4J"></a>SLF4J</h3><p>SLF4JModule 允许你使用 SLF4J 作为 Feign 的日志记录模块,这样你就可以轻松的使用 Logback, Log4J , 等来记录你的日志.</p><p>要在 Feign 中使用 SLF4J ,你需要添加SLF4J模块和对应的日志记录实现模块(比如Log4J)到你的环境变量,然后配置Feign使用Slf4jLogger :</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></pre></td><td class="code"><pre><span class="line">GitHub github = Feign.builder()</span><br><span class="line"> .logger(<span class="keyword">new</span> Slf4jLogger())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br></pre></td></tr></table></figure><p>Maven依赖:<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment"><!-- https://mvnrepository.com/artifact/com.netflix.feign/feign-gson --></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.feign<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>feign-slf4j<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>8.18.0<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></p><h3 id="Decoders"><a href="#Decoders" class="headerlink" title="Decoders"></a>Decoders</h3><p> <code>Feign.builder()</code> 允许你自定义一些额外的配置,比如说如何解码一个响应。假如有接口方法返回的消息不是 <code>Response</code>, <code>String</code>, <code>byte[]</code> 或者 <code>void</code> 类型的,那么你需要配置一个非默认的解码器。<br>下面是一个配置使用JSON解码器(使用的是feign-gson扩展)的例子:<br><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></pre></td><td class="code"><pre><span class="line">GitHub github = Feign.builder()</span><br><span class="line"> .decoder(<span class="keyword">new</span> GsonDecoder())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br></pre></td></tr></table></figure></p><p>假如你想在将响应传递给解码器处理前做一些额外的处理,那么你可以使用 <code>mapAndDecode</code> 方法。一个用例就是使用jsonp服务的时候:<br><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></pre></td><td class="code"><pre><span class="line">JsonpApi jsonpApi = Feign.builder()</span><br><span class="line"> .mapAndDecode((response, type) -> jsopUnwrap(response, type), <span class="keyword">new</span> GsonDecoder())</span><br><span class="line"> .target(JsonpApi.class, <span class="string">"https://some-jsonp-api.com"</span>);</span><br></pre></td></tr></table></figure></p><h3 id="Encoders"><a href="#Encoders" class="headerlink" title="Encoders"></a>Encoders</h3><p>发送一个Post请求最简单的方法就是传递一个 <code>String</code> 或者 <code>byte[]</code> 类型的参数了。你也许还需添加一个<code>Content-Type</code>请求头,如下:<br><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><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">LoginClient</span> </span>{</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"POST /"</span>)</span><br><span class="line"> <span class="meta">@Headers</span>(<span class="string">"Content-Type: application/json"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">login</span><span class="params">(String content)</span></span>;</span><br><span class="line">}</span><br><span class="line">...</span><br><span class="line">client.login(<span class="string">"{\"user_name\": \"denominator\", \"password\": \"secret\"}"</span>);</span><br></pre></td></tr></table></figure></p><p>通过配置一个解码器,你可以发送一个安全类型的请求体,如下是一个使用 feign-gson 扩展的例子:<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Credentials</span> </span>{</span><br><span class="line"> <span class="keyword">final</span> String user_name;</span><br><span class="line"> <span class="keyword">final</span> String password;</span><br><span class="line"></span><br><span class="line"> Credentials(String user_name, String password) {</span><br><span class="line"> <span class="keyword">this</span>.user_name = user_name;</span><br><span class="line"> <span class="keyword">this</span>.password = password;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">LoginClient</span> </span>{</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"POST /"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">login</span><span class="params">(Credentials creds)</span></span>;</span><br><span class="line">}</span><br><span class="line">...</span><br><span class="line">LoginClient client = Feign.builder()</span><br><span class="line"> .encoder(<span class="keyword">new</span> GsonEncoder())</span><br><span class="line"> .target(LoginClient.class, <span class="string">"https://foo.com"</span>);</span><br><span class="line"></span><br><span class="line">client.login(<span class="keyword">new</span> Credentials(<span class="string">"denominator"</span>, <span class="string">"secret"</span>));</span><br></pre></td></tr></table></figure></p><h3 id="Body-templates"><a href="#Body-templates" class="headerlink" title="@Body templates"></a>@Body templates</h3><p><code>@Body</code>注解申明一个请求体模板,模板中可以带有参数,与方法中 <code>@Param</code> 注解申明的参数相匹配,使用方法如下<br><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><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">LoginClient</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"POST /"</span>)</span><br><span class="line"> <span class="meta">@Headers</span>(<span class="string">"Content-Type: application/xml"</span>)</span><br><span class="line"> <span class="meta">@Body</span>(<span class="string">"<login \"user_name\"=\"{user_name}\" \"password\"=\"{password}\"/>"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">xml</span><span class="params">(@Param(<span class="string">"user_name"</span>)</span> String user, @<span class="title">Param</span><span class="params">(<span class="string">"password"</span>)</span> String password)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"POST /"</span>)</span><br><span class="line"> <span class="meta">@Headers</span>(<span class="string">"Content-Type: application/json"</span>)</span><br><span class="line"> <span class="comment">// json curly braces must be escaped!</span></span><br><span class="line"> <span class="comment">// 这里JSON格式需要的花括号居然需要转码,有点蛋疼了。</span></span><br><span class="line"> <span class="meta">@Body</span>(<span class="string">"%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">json</span><span class="params">(@Param(<span class="string">"user_name"</span>)</span> String user, @<span class="title">Param</span><span class="params">(<span class="string">"password"</span>)</span> String password)</span>;</span><br><span class="line">}</span><br><span class="line">...</span><br><span class="line"><span class="comment">// <login "user_name"="denominator" "password"="secret"/></span></span><br><span class="line">client.xml(<span class="string">"denominator"</span>, <span class="string">"secret"</span>);</span><br><span class="line"><span class="comment">// {"user_name": "denominator", "password": "secret"}</span></span><br><span class="line">client.json(<span class="string">"denominator"</span>, <span class="string">"secret"</span>);</span><br></pre></td></tr></table></figure></p><h3 id="Headers"><a href="#Headers" class="headerlink" title="Headers"></a>Headers</h3><p>Feign 支持给请求的api设置或者请求的客户端设置请求头</p><ul><li><p>使用 <code>@Headers</code> 设置静态请求头</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><span class="line"><span class="comment">// 给BaseApi中的所有方法设置Accept请求头</span></span><br><span class="line"><span class="meta">@Headers</span>(<span class="string">"Accept: application/json"</span>)</span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">BaseApi</span><<span class="title">V</span>> </span>{</span><br><span class="line"> <span class="comment">// 单独给put方法设置Content-Type请求头</span></span><br><span class="line"> <span class="meta">@Headers</span>(<span class="string">"Content-Type: application/json"</span>)</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"PUT /api/{key}"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">put</span><span class="params">(@Param(<span class="string">"key"</span>)</span> String, V value)</span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></li><li><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestLine</span>(<span class="string">"POST /"</span>)</span><br><span class="line"><span class="meta">@Headers</span>(<span class="string">"X-Ping: {token}"</span>)</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">post</span><span class="params">(@Param(<span class="string">"token"</span>)</span> String token)</span>;</span><br></pre></td></tr></table></figure></li><li><p>设置key和value都是动态的请求头<br>有些API需要根据调用时动态确定使用不同的请求头(e.g. custom metadata header fields such as “x-amz-meta-” or “x-goog-meta-“),这时候可以使用 <code>@HeaderMap</code> 注解,如下:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">// @HeaderMap 注解设置的请求头优先于其他方式设置的</span></span><br><span class="line"><span class="meta">@RequestLine</span>(<span class="string">"POST /"</span>)</span><br><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">post</span><span class="params">(@HeaderMap Map<String, Object> headerMap)</span></span>;</span><br></pre></td></tr></table></figure></li></ul><h3 id="给Target设置请求头"><a href="#给Target设置请求头" class="headerlink" title="给Target设置请求头"></a>给Target设置请求头</h3><p>有时我们需要在一个API实现中根据不同的endpoint来传入不同的Header,这个时候我们可以使用自定义的<code>RequestInterceptor</code> 或 <code>Target</code>来实现.<br>通过自定义的 <code>RequestInterceptor</code> 来实现请查看 <code>Request Interceptors</code> 章节.<br>下面是一个通过自定义<code>Targe</code>t来实现给每个<code>Target</code>设置安全校验信息Header的例子:<br><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></pre></td><td class="code"><pre><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">DynamicAuthTokenTarget</span><<span class="title">T</span>> <span class="keyword">implements</span> <span class="title">Target</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">DynamicAuthTokenTarget</span><span class="params">(Class<T> clazz,</span></span></span><br><span class="line"><span class="function"><span class="params"> UrlAndTokenProvider provider,</span></span></span><br><span class="line"><span class="function"><span class="params"> ThreadLocal<String> requestIdProvider)</span></span>;</span><br><span class="line"> ...</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Request <span class="title">apply</span><span class="params">(RequestTemplate input)</span> </span>{</span><br><span class="line"> TokenIdAndPublicURL urlAndToken = provider.get();</span><br><span class="line"> <span class="keyword">if</span> (input.url().indexOf(<span class="string">"http"</span>) != <span class="number">0</span>) {</span><br><span class="line"> input.insert(<span class="number">0</span>, urlAndToken.publicURL);</span><br><span class="line"> }</span><br><span class="line"> input.header(<span class="string">"X-Auth-Token"</span>, urlAndToken.tokenId);</span><br><span class="line"> input.header(<span class="string">"X-Request-ID"</span>, requestIdProvider.get());</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> input.request();</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">...</span><br><span class="line">Bank bank = Feign.builder()</span><br><span class="line"> .target(<span class="keyword">new</span> DynamicAuthTokenTarget(Bank.class, provider, requestIdProvider));</span><br></pre></td></tr></table></figure></p><p>这种方法的实现依赖于给Feign 客户端设置的自定义的<code>RequestInterceptor</code> 或 <code>Target</code>。可以被用来给一个客户端的所有api请求设置请求头。比如说可是被用来在header中设置身份校验信息。这些方法是在线程执行api请求的时候才会执行,所以是允许在运行时根据上下文来动态设置header的。<br>比如说可以根据线程本地存储(thread-local storage)来为不同的线程设置不同的请求头。</p><h3 id="高级用法"><a href="#高级用法" class="headerlink" title="高级用法"></a>高级用法</h3><h4 id="Base-APIS"><a href="#Base-APIS" class="headerlink" title="Base APIS"></a>Base APIS</h4><p>有些请求中的一些方法是通用的,但是可能会有不同的参数类型或者返回类型,这个时候可以这么用:<br><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><span class="line"><span class="comment">// 通用API</span></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">BaseAPI</span> </span>{</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /health"</span>)</span><br><span class="line"> <span class="function">String <span class="title">health</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /all"</span>)</span><br><span class="line"> <span class="function">List<Entity> <span class="title">all</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 继承通用API</span></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">CustomAPI</span> <span class="keyword">extends</span> <span class="title">BaseAPI</span> </span>{</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /custom"</span>)</span><br><span class="line"> <span class="function">String <span class="title">custom</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 各种类型有相同的表现形式,定义一个统一的API</span></span><br><span class="line"><span class="meta">@Headers</span>(<span class="string">"Accept: application/json"</span>)</span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">BaseApi</span><<span class="title">V</span>> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /api/{key}"</span>)</span><br><span class="line"> <span class="function">V <span class="title">get</span><span class="params">(@Param(<span class="string">"key"</span>)</span> String key)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /api"</span>)</span><br><span class="line"> <span class="function">List<V> <span class="title">list</span><span class="params">()</span></span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Headers</span>(<span class="string">"Content-Type: application/json"</span>)</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"PUT /api/{key}"</span>)</span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">put</span><span class="params">(@Param(<span class="string">"key"</span>)</span> String key, V value)</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">// 根据不同的类型来继承</span></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">FooApi</span> <span class="keyword">extends</span> <span class="title">BaseApi</span><<span class="title">Foo</span>> </span>{ }</span><br><span class="line"></span><br><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">BarApi</span> <span class="keyword">extends</span> <span class="title">BaseApi</span><<span class="title">Bar</span>> </span>{ }</span><br></pre></td></tr></table></figure></p><h4 id="Logging"><a href="#Logging" class="headerlink" title="Logging"></a>Logging</h4><p>你可以通过设置一个 <code>Logger</code> 来记录http消息,如下:<br><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></pre></td><td class="code"><pre><span class="line">GitHub github = Feign.builder()</span><br><span class="line"> .decoder(<span class="keyword">new</span> GsonDecoder())</span><br><span class="line"> .logger(<span class="keyword">new</span> Logger.JavaLogger().appendToFile(<span class="string">"logs/http.log"</span>))</span><br><span class="line"> .logLevel(Logger.Level.FULL)</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br></pre></td></tr></table></figure></p><p>也可以参考上面的 SLF4J 章节的说明</p><h4 id="Request-Interceptors"><a href="#Request-Interceptors" class="headerlink" title="Request Interceptors"></a>Request Interceptors</h4><p>当你希望修改所有的的请求的时候,你可以使用<code>Request Interceptors</code>。比如说,你作为一个中介,你可能需要为每个请求设置 <code>X-Forwarded-For</code><br><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><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">ForwardedForInterceptor</span> <span class="keyword">implements</span> <span class="title">RequestInterceptor</span> </span>{</span><br><span class="line"> <span class="meta">@Override</span> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">apply</span><span class="params">(RequestTemplate template)</span> </span>{</span><br><span class="line"> template.header(<span class="string">"X-Forwarded-For"</span>, <span class="string">"origin.host.com"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line">...</span><br><span class="line">Bank bank = Feign.builder()</span><br><span class="line"> .decoder(accountDecoder)</span><br><span class="line"> .requestInterceptor(<span class="keyword">new</span> ForwardedForInterceptor())</span><br><span class="line"> .target(Bank.class, <span class="string">"https://api.examplebank.com"</span>);</span><br></pre></td></tr></table></figure></p><p>或者,你可能需要实现Basic Auth,这里有一个内置的基础校验拦截器 <code>BasicAuthRequestInterceptor</code><br><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><span class="line">Bank bank = Feign.builder()</span><br><span class="line"> .decoder(accountDecoder)</span><br><span class="line"> .requestInterceptor(<span class="keyword">new</span> BasicAuthRequestInterceptor(username, password))</span><br><span class="line"> .target(Bank.class, <span class="string">"https://api.examplebank.com"</span>);</span><br></pre></td></tr></table></figure></p><h4 id="Custom-Param-Expansion"><a href="#Custom-Param-Expansion" class="headerlink" title="Custom @Param Expansion"></a>Custom @Param Expansion</h4><p>在使用 <code>@Param</code> 注解给模板中的参数设值的时候,默认的是使用的对象的 <code>toString()</code> 方法的值,通过声明 自定义的<code>Param.Expander</code>,用户可以控制其行为,比如说格式化 <code>Date</code> 类型的值:<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">// 通过设置 @Param 的 expander 为 DateToMillis.class 可以定义Date类型的值</span></span><br><span class="line"><span class="meta">@RequestLine</span>(<span class="string">"GET /?since={date}"</span>)</span><br><span class="line"><span class="function">Result <span class="title">list</span><span class="params">(@Param(value = <span class="string">"date"</span>, expander = DateToMillis.class)</span> Date date)</span>;</span><br></pre></td></tr></table></figure></p><h4 id="Dynamic-Query-Parameters"><a href="#Dynamic-Query-Parameters" class="headerlink" title="Dynamic Query Parameters"></a>Dynamic Query Parameters</h4><p>动态查询参数支持,通过使用 <code>@QueryMap</code> 可以允许动态传入请求参数,如下:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RequestLine</span>(<span class="string">"GET /find"</span>)</span><br><span class="line"><span class="function">V <span class="title">find</span><span class="params">(@QueryMap Map<String, Object> queryMap)</span></span>;</span><br></pre></td></tr></table></figure></p><h4 id="Static-and-Default-Methods"><a href="#Static-and-Default-Methods" class="headerlink" title="Static and Default Methods"></a>Static and Default Methods</h4><p>如果你使用的是JDK 1.8+ 的话,那么你可以给接口设置统一的默认方法和静态方法,这个事JDK8的新特性,如下:<br><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></pre></td><td class="code"><pre><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">GitHub</span> </span>{</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /repos/{owner}/{repo}/contributors"</span>)</span><br><span class="line"> <span class="function">List<Contributor> <span class="title">contributors</span><span class="params">(@Param(<span class="string">"owner"</span>)</span> String owner, @<span class="title">Param</span><span class="params">(<span class="string">"repo"</span>)</span> String repo)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /users/{username}/repos?sort={sort}"</span>)</span><br><span class="line"> <span class="function">List<Repo> <span class="title">repos</span><span class="params">(@Param(<span class="string">"username"</span>)</span> String owner, @<span class="title">Param</span><span class="params">(<span class="string">"sort"</span>)</span> String sort)</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">default</span> List<Repo> <span class="title">repos</span><span class="params">(String owner)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> repos(owner, <span class="string">"full_name"</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">/**</span></span><br><span class="line"><span class="comment"> * Lists all contributors for all repos owned by a user.</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"> <span class="function"><span class="keyword">default</span> List<Contributor> <span class="title">contributors</span><span class="params">(String user)</span> </span>{</span><br><span class="line"> MergingContributorList contributors = <span class="keyword">new</span> MergingContributorList();</span><br><span class="line"> <span class="keyword">for</span>(Repo repo : <span class="keyword">this</span>.repos(owner)) {</span><br><span class="line"> contributors.addAll(<span class="keyword">this</span>.contributors(user, repo.getName()));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> contributors.mergeResult();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">static</span> GitHub <span class="title">connect</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> Feign.builder()</span><br><span class="line"> .decoder(<span class="keyword">new</span> GsonDecoder())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p>]]></content>
<summary type="html">
<p>英文原文请参考:<a href="https://github.com/OpenFeign/feign" target="_blank" rel="noopener">https://github.com/OpenFeign/feign</a><br>翻译参考:<a href="https://blog.csdn.net/u010862794/article/details/73649616" target="_blank" rel="noopener">https://blog.csdn.net/u010862794/article/details/73649616</a></p>
<h2 id="简介"><a href="#简介" class="headerlink" title="简介"></a>简介</h2><p>Feign是一款java的Restful客户端组件,Feign使得 Java HTTP 客户端编写更方便。Feign 灵感来源于<a href="https://github.com/square/retrofit" target="_blank" rel="noopener">Retrofit</a>, <a href="https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html" target="_blank" rel="noopener">JAXRS-2.0</a>和<a href="http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html" target="_blank" rel="noopener">WebSocket</a>。Feign 最初是为了降低统一绑定<a href="https://github.com/Netflix/Denominator" target="_blank" rel="noopener">Denominator</a> 到 HTTP API 的复杂度,不区分是否支持 <a href="http://www.slideshare.net/adrianfcole/99problems" target="_blank" rel="noopener">ReSTfulness</a>。</p>
</summary>
<category term="spring" scheme="http://along101.site/categories/spring/"/>
<category term="java" scheme="http://along101.site/tags/java/"/>
<category term="rest" scheme="http://along101.site/tags/rest/"/>
<category term="feign" scheme="http://along101.site/tags/feign/"/>
</entry>
<entry>
<title>Feign源码解析</title>
<link href="http://along101.site/2018/06/13/feign-source-analysis/"/>
<id>http://along101.site/2018/06/13/feign-source-analysis/</id>
<published>2018-06-13T12:03:13.000Z</published>
<updated>2018-07-20T03:10:04.401Z</updated>
<content type="html"><![CDATA[<h2 id="Feign介绍"><a href="#Feign介绍" class="headerlink" title="Feign介绍"></a>Feign介绍</h2><p><a href="https://github.com/OpenFeign/feign/" target="_blank" rel="noopener">Feign</a>是一款java的Restful客户端组件,Feign使得 Java HTTP 客户端编写更方便。Feign 灵感来源于<a href="https://github.com/square/retrofit" target="_blank" rel="noopener">Retrofit</a>, <a href="https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html" target="_blank" rel="noopener">JAXRS-2.0</a>和<a href="http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html" target="_blank" rel="noopener">WebSocket</a>。feign在github上有近3K个star,是一款相当优秀的开源组件,虽然相比Retrofit的近30K个star,逊色了太多,但是spring cloud集成了feign,使得feign在java生态中比Retrofit使用的更加广泛。</p><p>feign的基本原理是在接口方法上加注解,定义rest请求,构造出接口的动态代理对象,然后通过调用接口方法就可以发送http请求,并且自动解析http响应为方法返回值,极大的简化了客户端调用rest api的代码。官网的示例如下:</p><a id="more"></a><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><span class="line"><span class="class"><span class="keyword">interface</span> <span class="title">GitHub</span> </span>{</span><br><span class="line"> <span class="meta">@RequestLine</span>(<span class="string">"GET /repos/{owner}/{repo}/contributors"</span>)</span><br><span class="line"> <span class="function">List<Contributor> <span class="title">contributors</span><span class="params">(@Param(<span class="string">"owner"</span>)</span> String owner, @<span class="title">Param</span><span class="params">(<span class="string">"repo"</span>)</span> String repo)</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="keyword">static</span> <span class="class"><span class="keyword">class</span> <span class="title">Contributor</span> </span>{</span><br><span class="line"> String login;</span><br><span class="line"> <span class="keyword">int</span> contributions;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String... args)</span> </span>{</span><br><span class="line"> GitHub github = Feign.builder()</span><br><span class="line"> .decoder(<span class="keyword">new</span> GsonDecoder())</span><br><span class="line"> .target(GitHub.class, <span class="string">"https://api.github.com"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="comment">// Fetch and print a list of the contributors to this library.</span></span><br><span class="line"> List<Contributor> contributors = github.contributors(<span class="string">"OpenFeign"</span>, <span class="string">"feign"</span>);</span><br><span class="line"> <span class="keyword">for</span> (Contributor contributor : contributors) {</span><br><span class="line"> System.out.println(contributor.login + <span class="string">" ("</span> + contributor.contributions + <span class="string">")"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>feign使用教程请参考官网<a href="https://github.com/OpenFeign/feign/" target="_blank" rel="noopener">https://github.com/OpenFeign/feign/</a></p><p>本文主要是对feign源码进行分析,根据源码来理解feign的设计架构和内部实现技术。 </p><h2 id="Feign-build-构建接口动态代理"><a href="#Feign-build-构建接口动态代理" class="headerlink" title="Feign.build 构建接口动态代理"></a><code>Feign.build</code> 构建接口动态代理</h2><p>我们先来看看接口的动态代理是如何构建出来的,下图是主要接口和类的类图:</p><p><img src="/2018/06/13/feign-source-analysis/init1.png" alt=""></p><p>从上文中的示例可以看到,构建的接口动态代理对象是通过<code>Feign.builder()</code>生成<code>Feign.Builder</code>的构造者对象,然后设置相关的参数,再调用target方法构造的。<code>Feign.Builder</code>的参数包括:</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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">//拦截器,组装完RequestTemplate,发请求之前的拦截处理RequestTemplate</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> List<RequestInterceptor> requestInterceptors = <span class="keyword">new</span> ArrayList<RequestInterceptor>();</span><br><span class="line"><span class="comment">//日志级别</span></span><br><span class="line"> <span class="keyword">private</span> Logger.Level logLevel = Logger.Level.NONE;</span><br><span class="line"><span class="comment">//契约模型,默认为Contract.Default,用户创建MethodMetadata,用spring cloud就是扩展这个实现springMVC注解</span></span><br><span class="line"> <span class="keyword">private</span> Contract contract = <span class="keyword">new</span> Contract.Default();</span><br><span class="line"><span class="comment">//客户端,默认为Client.Default,可以扩展ApacheHttpClient,OKHttpClient,RibbonClient等</span></span><br><span class="line"> <span class="keyword">private</span> Client client = <span class="keyword">new</span> Client.Default(<span class="keyword">null</span>, <span class="keyword">null</span>);</span><br><span class="line"><span class="comment">//重试设置,默认不设置</span></span><br><span class="line"> <span class="keyword">private</span> Retryer retryer = <span class="keyword">new</span> Retryer.Default();</span><br><span class="line"><span class="comment">//日志,可以接入Slf4j</span></span><br><span class="line"> <span class="keyword">private</span> Logger logger = <span class="keyword">new</span> NoOpLogger();</span><br><span class="line"><span class="comment">//编码器,用于body的编码</span></span><br><span class="line"> <span class="keyword">private</span> Encoder encoder = <span class="keyword">new</span> Encoder.Default();</span><br><span class="line"><span class="comment">//解码器,用户response的解码</span></span><br><span class="line"> <span class="keyword">private</span> Decoder decoder = <span class="keyword">new</span> Decoder.Default();</span><br><span class="line"><span class="comment">//用@QueryMap注解的参数编码器</span></span><br><span class="line"> <span class="keyword">private</span> QueryMapEncoder queryMapEncoder = <span class="keyword">new</span> QueryMapEncoder.Default();</span><br><span class="line"><span class="comment">//请求错误解码器</span></span><br><span class="line"> <span class="keyword">private</span> ErrorDecoder errorDecoder = <span class="keyword">new</span> ErrorDecoder.Default();</span><br><span class="line"><span class="comment">//参数配置,主要是超时时间之类的</span></span><br><span class="line"> <span class="keyword">private</span> Options options = <span class="keyword">new</span> Options();</span><br><span class="line"><span class="comment">//动态代理工厂</span></span><br><span class="line"> <span class="keyword">private</span> InvocationHandlerFactory invocationHandlerFactory = <span class="keyword">new</span> InvocationHandlerFactory.Default();</span><br><span class="line"><span class="comment">//是否decode404</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> decode404;</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> closeAfterDecode = <span class="keyword">true</span>;</span><br></pre></td></tr></table></figure><p>这块是一个典型的构造者模式,<code>target</code>方法内部先调用<code>build</code>方法新建一个<code>ReflectFeign</code>对象,然后调用<code>ReflectFeign</code>的<code>newInstance</code>方法创建动态代理,代码如下:</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><span class="line"> <span class="comment">//默认使用HardCodedTarget</span></span><br><span class="line"> <span class="keyword">public</span> <T> <span class="function">T <span class="title">target</span><span class="params">(Class<T> apiType, String url)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> target(<span class="keyword">new</span> HardCodedTarget<T>(apiType, url));</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">public</span> <T> <span class="function">T <span class="title">target</span><span class="params">(Target<T> target)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> build().newInstance(target);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Feign <span class="title">build</span><span class="params">()</span> </span>{</span><br><span class="line"> SynchronousMethodHandler.Factory synchronousMethodHandlerFactory =</span><br><span class="line"> <span class="keyword">new</span> SynchronousMethodHandler.Factory(client, retryer, requestInterceptors, logger,</span><br><span class="line"> logLevel, decode404, closeAfterDecode);</span><br><span class="line"> ParseHandlersByName handlersByName =</span><br><span class="line"> <span class="keyword">new</span> ParseHandlersByName(contract, options, encoder, decoder, queryMapEncoder,</span><br><span class="line"> errorDecoder, synchronousMethodHandlerFactory);</span><br><span class="line"> <span class="comment">//handlersByName将所有参数进行封装,并提供解析接口方法的逻辑</span></span><br><span class="line"> <span class="comment">//invocationHandlerFactory是Builder的属性,默认值是InvocationHandlerFactory.Default,用创建java动态代理的InvocationHandler实现</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ReflectiveFeign(handlersByName, invocationHandlerFactory, queryMapEncoder);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>ReflectiveFeign</code>构造函数有三个参数:</p><ul><li><code>ParseHandlersByName</code> 将builder所有参数进行封装,并提供解析接口方法的逻辑</li><li><code>InvocationHandlerFactory</code> java动态代理的<code>InvocationHandler</code>的工厂类,默认值是<code>InvocationHandlerFactory.Default</code></li><li><code>QueryMapEncoder</code> 接口参数注解<code>@QueryMap</code>时,参数的编码器</li></ul><p><code>ReflectiveFeign.newInstance</code>方法创建接口动态代理对象:<br><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="keyword">public</span> <T> <span class="function">T <span class="title">newInstance</span><span class="params">(Target<T> target)</span> </span>{</span><br><span class="line"> <span class="comment">//targetToHandlersByName是构造器传入的ParseHandlersByName对象,根据target对象生成MethodHandler映射</span></span><br><span class="line"> Map<String, MethodHandler> nameToHandler = targetToHandlersByName.apply(target);</span><br><span class="line"> Map<Method, MethodHandler> methodToHandler = <span class="keyword">new</span> LinkedHashMap<Method, MethodHandler>();</span><br><span class="line"> List<DefaultMethodHandler> defaultMethodHandlers = <span class="keyword">new</span> LinkedList<DefaultMethodHandler>();</span><br><span class="line"> <span class="comment">//遍历接口所有方法,构建Method->MethodHandler的映射</span></span><br><span class="line"> <span class="keyword">for</span> (Method method : target.type().getMethods()) {</span><br><span class="line"> <span class="keyword">if</span> (method.getDeclaringClass() == Object.class) {</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span>(Util.isDefault(method)) {</span><br><span class="line"> <span class="comment">//接口default方法的Handler,这类方法直接调用</span></span><br><span class="line"> DefaultMethodHandler handler = <span class="keyword">new</span> DefaultMethodHandler(method);</span><br><span class="line"> defaultMethodHandlers.add(handler);</span><br><span class="line"> methodToHandler.put(method, handler);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> methodToHandler.put(method, nameToHandler.get(Feign.configKey(target.type(), method)));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//这里factory是构造其中传入的,创建InvocationHandler</span></span><br><span class="line"> InvocationHandler handler = factory.create(target, methodToHandler);</span><br><span class="line"> <span class="comment">//java的动态代理</span></span><br><span class="line"> T proxy = (T) Proxy.newProxyInstance(target.type().getClassLoader(), <span class="keyword">new</span> Class<?>[]{target.type()}, handler);</span><br><span class="line"> <span class="comment">//将default方法直接绑定到动态代理上</span></span><br><span class="line"> <span class="keyword">for</span>(DefaultMethodHandler defaultMethodHandler : defaultMethodHandlers) {</span><br><span class="line"> defaultMethodHandler.bindTo(proxy);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> proxy;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这段代码主要的逻辑是:</p><ol><li>创建<code>MethodHandler</code>的映射,这里创建的是实现类<code>SynchronousMethodHandler</code></li><li>通过<code>InvocationHandlerFatory</code>创建<code>InvocationHandler</code></li><li>绑定接口的<code>default</code>方法,通过<code>DefaultMethodHandler</code>绑定</li></ol><p>类图中已经画出,<code>SynchronousMethodHandler</code>和<code>DefaultMethodHandler</code>实现了<code>InvocationHandlerFactory.MethodHandler</code>接口,动态代理对象调用方法时,如果是<code>default</code>方法,会直接调用接口方法,因为这里将接口的<code>default</code>方法绑定到动态代理对象上了,其他方法根据方法签名找到<code>SynchronousMethodHandler</code>对象,调用其<code>invoke</code>方法。</p><h2 id="创建MethodHandler方法处理器"><a href="#创建MethodHandler方法处理器" class="headerlink" title="创建MethodHandler方法处理器"></a>创建MethodHandler方法处理器</h2><p><code>SynchronousMethodHandler</code>是feign组件的核心,接口方法调用转换为http请求和解析http响应都是通过<code>SynchronousMethodHandler</code>来执行的,相关类图如下:</p><p><img src="/2018/06/13/feign-source-analysis/init2.png" alt=""></p><p>创建<code>MethodHandler</code>实现类<code>SynchronousMethodHandler</code>的代码:<br><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><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> Map<String, MethodHandler> <span class="title">apply</span><span class="params">(Target key)</span> </span>{</span><br><span class="line"> <span class="comment">//通过contract解析接口方法,生成MethodMetadata列表,默认的contract解析Feign自定义的http注解</span></span><br><span class="line"> List<MethodMetadata> metadata = contract.parseAndValidatateMetadata(key.type());</span><br><span class="line"> Map<String, MethodHandler> result = <span class="keyword">new</span> LinkedHashMap<String, MethodHandler>();</span><br><span class="line"> <span class="keyword">for</span> (MethodMetadata md : metadata) {</span><br><span class="line"> <span class="comment">//BuildTemplateByResolvingArgs实现RequestTemplate.Factory,RequestTemplate的工厂</span></span><br><span class="line"> BuildTemplateByResolvingArgs buildTemplate;</span><br><span class="line"> <span class="comment">//根据方法元数据,使用不同的RequestTemplate的工厂</span></span><br><span class="line"> <span class="keyword">if</span> (!md.formParams().isEmpty() && md.template().bodyTemplate() == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//如果有formParam,并且bodyTemplate不为空,请求体为x-www-form-urlencoded格式</span></span><br><span class="line"> <span class="comment">//将会解析form参数,填充到bodyTemplate中</span></span><br><span class="line"> buildTemplate = <span class="keyword">new</span> BuildFormEncodedTemplateFromArgs(md, encoder, queryMapEncoder);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (md.bodyIndex() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//如果包含请求体,将会用encoder编码请求体对象</span></span><br><span class="line"> buildTemplate = <span class="keyword">new</span> BuildEncodedTemplateFromArgs(md, encoder, queryMapEncoder);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//默认的RequestTemplate的工厂,没有请求体,不需要编码器</span></span><br><span class="line"> buildTemplate = <span class="keyword">new</span> BuildTemplateByResolvingArgs(md, queryMapEncoder);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//使用工厂SynchronousMethodHandler.Factory创建SynchronousMethodHandler</span></span><br><span class="line"> result.put(md.configKey(),</span><br><span class="line"> factory.create(key, md, buildTemplate, options, decoder, errorDecoder));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这段代码的逻辑是:</p><ol><li>通过<code>Contract</code>解析接口方法,生成<code>MethodMetadata</code>,默认的<code>Contract</code>解析Feign自定义的http注解</li><li>根据<code>MethodMetadata</code>方法元数据生成特定的<code>RequestTemplate</code>的工厂</li><li>使用<code>SynchronousMethodHandler.Factory</code>工厂创建<code>SynchronousMethodHandler</code><br>这里有两个工厂不要搞混淆了,<code>SynchronousMethodHandler</code>工厂和<code>RequestTemplate</code>工厂,<code>SynchronousMethodHandler</code>的属性包含<code>RequestTemplate</code>工厂</li></ol><h2 id="Contract解析接口方法生成MethodMetadata"><a href="#Contract解析接口方法生成MethodMetadata" class="headerlink" title="Contract解析接口方法生成MethodMetadata"></a><code>Contract</code>解析接口方法生成<code>MethodMetadata</code></h2><p>feign默认的解析器是<code>Contract.Default</code>继承了<code>Contract.BaseContract</code>,解析生成<code>MethodMetadata</code>方法入口:<br><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><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><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br><span class="line">161</span><br><span class="line">162</span><br><span class="line">163</span><br><span class="line">164</span><br><span class="line">165</span><br><span class="line">166</span><br></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> List<MethodMetadata> <span class="title">parseAndValidatateMetadata</span><span class="params">(Class<?> targetType)</span> </span>{</span><br><span class="line"> 。。。</span><br><span class="line"> Map<String, MethodMetadata> result = <span class="keyword">new</span> LinkedHashMap<String, MethodMetadata>();</span><br><span class="line"> <span class="keyword">for</span> (Method method : targetType.getMethods()) {</span><br><span class="line"> 。。。</span><br><span class="line"> MethodMetadata metadata = parseAndValidateMetadata(targetType, method);</span><br><span class="line"> 。。。</span><br><span class="line"> result.put(metadata.configKey(), metadata);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> ArrayList<MethodMetadata>(result.values());</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> MethodMetadata <span class="title">parseAndValidateMetadata</span><span class="params">(Class<?> targetType, Method method)</span> </span>{</span><br><span class="line"> MethodMetadata data = <span class="keyword">new</span> MethodMetadata();</span><br><span class="line"> data.returnType(Types.resolve(targetType, targetType, method.getGenericReturnType()));</span><br><span class="line"> data.configKey(Feign.configKey(targetType, method));</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span>(targetType.getInterfaces().length == <span class="number">1</span>) {</span><br><span class="line"> processAnnotationOnClass(data, targetType.getInterfaces()[<span class="number">0</span>]);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//处理Class上的注解</span></span><br><span class="line"> processAnnotationOnClass(data, targetType);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (Annotation methodAnnotation : method.getAnnotations()) {</span><br><span class="line"> <span class="comment">//处理方法注解</span></span><br><span class="line"> processAnnotationOnMethod(data, methodAnnotation, method);</span><br><span class="line"> }</span><br><span class="line"> 。。。</span><br><span class="line"> Class<?>[] parameterTypes = method.getParameterTypes();</span><br><span class="line"> Type[] genericParameterTypes = method.getGenericParameterTypes();</span><br><span class="line"> <span class="comment">//方法参数注解</span></span><br><span class="line"> Annotation[][] parameterAnnotations = method.getParameterAnnotations();</span><br><span class="line"> <span class="keyword">int</span> count = parameterAnnotations.length;</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < count; i++) {</span><br><span class="line"> <span class="keyword">boolean</span> isHttpAnnotation = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">if</span> (parameterAnnotations[i] != <span class="keyword">null</span>) {</span><br><span class="line"> isHttpAnnotation = processAnnotationsOnParameter(data, parameterAnnotations[i], i);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (parameterTypes[i] == URI.class) {</span><br><span class="line"> <span class="comment">//参数类型是URI,后面构造http请求时,使用该URI</span></span><br><span class="line"> data.urlIndex(i);</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (!isHttpAnnotation) {</span><br><span class="line"> <span class="comment">//如果没有被http注解,就是body参数</span></span><br><span class="line"> 。。。</span><br><span class="line"> data.bodyIndex(i);</span><br><span class="line"> data.bodyType(Types.resolve(targetType, targetType, genericParameterTypes[i]));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (data.headerMapIndex() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//@HeaderMap注解的参数必须是Map,key类型必须是String</span></span><br><span class="line"> checkMapString(<span class="string">"HeaderMap"</span>, parameterTypes[data.headerMapIndex()], genericParameterTypes[data.headerMapIndex()]);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (data.queryMapIndex() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">if</span> (Map.class.isAssignableFrom(parameterTypes[data.queryMapIndex()])) {</span><br><span class="line"> <span class="comment">//@QueryMap注解的参数如果是Map,key类型必须是String</span></span><br><span class="line"> checkMapKeys(<span class="string">"QueryMap"</span>, genericParameterTypes[data.queryMapIndex()]);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> data;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">processAnnotationOnClass</span><span class="params">(MethodMetadata data, Class<?> targetType)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (targetType.isAnnotationPresent(Headers.class)) {</span><br><span class="line"> <span class="comment">//被Headers注解</span></span><br><span class="line"> String[] headersOnType = targetType.getAnnotation(Headers.class).value();</span><br><span class="line"> 。。。</span><br><span class="line"> <span class="comment">//header解析成map,加到MethodMetadata中</span></span><br><span class="line"> Map<String, Collection<String>> headers = toMap(headersOnType);</span><br><span class="line"> headers.putAll(data.template().headers());</span><br><span class="line"> data.template().headers(<span class="keyword">null</span>); <span class="comment">// to clear</span></span><br><span class="line"> data.template().headers(headers);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">processAnnotationOnMethod</span><span class="params">(MethodMetadata data, Annotation methodAnnotation,</span></span></span><br><span class="line"><span class="function"><span class="params"> Method method)</span> </span>{</span><br><span class="line"> Class<? extends Annotation> annotationType = methodAnnotation.annotationType();</span><br><span class="line"> <span class="keyword">if</span> (annotationType == RequestLine.class) {</span><br><span class="line"> <span class="comment">//@RequestLine注解</span></span><br><span class="line"> String requestLine = RequestLine.class.cast(methodAnnotation).value();</span><br><span class="line"> 。。。</span><br><span class="line"> <span class="keyword">if</span> (requestLine.indexOf(<span class="string">' '</span>) == -<span class="number">1</span>) {</span><br><span class="line"> 。。。</span><br><span class="line"> data.template().method(requestLine);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//http请求方法</span></span><br><span class="line"> data.template().method(requestLine.substring(<span class="number">0</span>, requestLine.indexOf(<span class="string">' '</span>)));</span><br><span class="line"> <span class="keyword">if</span> (requestLine.indexOf(<span class="string">' '</span>) == requestLine.lastIndexOf(<span class="string">' '</span>)) {</span><br><span class="line"> <span class="comment">// no HTTP version is ok</span></span><br><span class="line"> data.template().append(requestLine.substring(requestLine.indexOf(<span class="string">' '</span>) + <span class="number">1</span>));</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">// skip HTTP version</span></span><br><span class="line"> data.template().append(</span><br><span class="line"> requestLine.substring(requestLine.indexOf(<span class="string">' '</span>) + <span class="number">1</span>, requestLine.lastIndexOf(<span class="string">' '</span>)));</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//将'%2F'反转为'/'</span></span><br><span class="line"> data.template().decodeSlash(RequestLine.class.cast(methodAnnotation).decodeSlash());</span><br><span class="line"> <span class="comment">//参数集合格式化方式,默认使用key=value0&key=value1</span></span><br><span class="line"> data.template().collectionFormat(RequestLine.class.cast(methodAnnotation).collectionFormat());</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (annotationType == Body.class) {</span><br><span class="line"> <span class="comment">//@Body注解</span></span><br><span class="line"> String body = Body.class.cast(methodAnnotation).value();</span><br><span class="line"> 。。。</span><br><span class="line"> <span class="keyword">if</span> (body.indexOf(<span class="string">'{'</span>) == -<span class="number">1</span>) {</span><br><span class="line"> <span class="comment">//body中不存在{,直接传入body</span></span><br><span class="line"> data.template().body(body);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//body中存在{,就是bodyTemplate方式</span></span><br><span class="line"> data.template().bodyTemplate(body);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (annotationType == Headers.class) {</span><br><span class="line"> <span class="comment">//@Header注解</span></span><br><span class="line"> String[] headersOnMethod = Headers.class.cast(methodAnnotation).value();</span><br><span class="line"> 。。。</span><br><span class="line"> data.template().headers(toMap(headersOnMethod));</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//处理参数上的注解</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">processAnnotationsOnParameter</span><span class="params">(MethodMetadata data, Annotation[] annotations, <span class="keyword">int</span> paramIndex)</span> </span>{</span><br><span class="line"> <span class="keyword">boolean</span> isHttpAnnotation = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">for</span> (Annotation annotation : annotations) {</span><br><span class="line"> Class<? extends Annotation> annotationType = annotation.annotationType();</span><br><span class="line"> <span class="keyword">if</span> (annotationType == Param.class) {</span><br><span class="line"> <span class="comment">//@Param注解</span></span><br><span class="line"> Param paramAnnotation = (Param) annotation;</span><br><span class="line"> String name = paramAnnotation.value();</span><br><span class="line"> 。。。</span><br><span class="line"> <span class="comment">//增加到MethodMetadata中</span></span><br><span class="line"> nameParam(data, name, paramIndex);</span><br><span class="line"> <span class="comment">//@Param注解的expander参数,定义参数的解释器,默认是ToStringExpander,调用参数的toString方法</span></span><br><span class="line"> Class<? extends Param.Expander> expander = paramAnnotation.expander();</span><br><span class="line"> <span class="keyword">if</span> (expander != Param.ToStringExpander.class) {</span><br><span class="line"> data.indexToExpanderClass().put(paramIndex, expander);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//参数是否已经urlEncoded,如果没有,会使用urlEncoded方式编码</span></span><br><span class="line"> data.indexToEncoded().put(paramIndex, paramAnnotation.encoded());</span><br><span class="line"> isHttpAnnotation = <span class="keyword">true</span>;</span><br><span class="line"> String varName = <span class="string">'{'</span> + name + <span class="string">'}'</span>;</span><br><span class="line"> <span class="keyword">if</span> (!data.template().url().contains(varName) &&</span><br><span class="line"> !searchMapValuesContainsSubstring(data.template().queries(), varName) &&</span><br><span class="line"> !searchMapValuesContainsSubstring(data.template().headers(), varName)) {</span><br><span class="line"> <span class="comment">//如果参数不在path里面,不在query里面,不在header里面,就设置到formParam中</span></span><br><span class="line"> data.formParams().add(name);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (annotationType == QueryMap.class) {</span><br><span class="line"> <span class="comment">//@QueryMap注解,注解参数对象时,将该参数转换为http请求参数格式发送</span></span><br><span class="line"> 。。。</span><br><span class="line"> data.queryMapIndex(paramIndex);</span><br><span class="line"> data.queryMapEncoded(QueryMap.class.cast(annotation).encoded());</span><br><span class="line"> isHttpAnnotation = <span class="keyword">true</span>;</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (annotationType == HeaderMap.class) {</span><br><span class="line"> <span class="comment">//@HeaderMap注解,注解一个Map类型的参数,放入http header中发送</span></span><br><span class="line"> 。。。</span><br><span class="line"> data.headerMapIndex(paramIndex);</span><br><span class="line"> isHttpAnnotation = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> isHttpAnnotation;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>代码稍微有点多,但是逻辑很清晰,先处理类上的注解,再处理方法上注解,最后处理方法参数注解,把所有注解的情况都处理到就可以了。 </p><p>生成的<code>MethodMetadata</code>的结构如下:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">final</span> <span class="class"><span class="keyword">class</span> <span class="title">MethodMetadata</span> <span class="keyword">implements</span> <span class="title">Serializable</span> </span>{</span><br><span class="line"> <span class="comment">//标识方法的key,接口名加方法签名:GitHub#contributors(String,String)</span></span><br><span class="line"> <span class="keyword">private</span> String configKey;</span><br><span class="line"><span class="comment">//方法返回值类型</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">transient</span> Type returnType;</span><br><span class="line"><span class="comment">//uri参数的位置,方法中可以写个uri参数,发请求时直接使用这个参数</span></span><br><span class="line"> <span class="keyword">private</span> Integer urlIndex;</span><br><span class="line"><span class="comment">//body参数的位置,只能有一个未注解的参数为body,否则报错</span></span><br><span class="line"> <span class="keyword">private</span> Integer bodyIndex;</span><br><span class="line"><span class="comment">//headerMap参数的位置</span></span><br><span class="line"> <span class="keyword">private</span> Integer headerMapIndex;</span><br><span class="line"><span class="comment">//@QueryMap注解参数位置</span></span><br><span class="line"> <span class="keyword">private</span> Integer queryMapIndex;</span><br><span class="line"><span class="comment">//@QueryMap注解里面encode参数,是否已经urlEncode编码过了</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> queryMapEncoded;</span><br><span class="line"><span class="comment">//body的类型</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">transient</span> Type bodyType;</span><br><span class="line"><span class="comment">//RequestTemplate 原型</span></span><br><span class="line"> <span class="keyword">private</span> RequestTemplate template = <span class="keyword">new</span> RequestTemplate();</span><br><span class="line"><span class="comment">//form请求参数</span></span><br><span class="line"> <span class="keyword">private</span> List<String> formParams = <span class="keyword">new</span> ArrayList<String>();</span><br><span class="line"><span class="comment">//方法参数位置和名称的map</span></span><br><span class="line"> <span class="keyword">private</span> Map<Integer, Collection<String>> indexToName ;</span><br><span class="line"><span class="comment">//@Param中注解的expander方法,可以指定解析参数类</span></span><br><span class="line"> <span class="keyword">private</span> Map<Integer, Class<? extends Expander>> indexToExpanderClass ;</span><br><span class="line"><span class="comment">//参数是否被urlEncode编码过了,@Param中encoded方法</span></span><br><span class="line"> <span class="keyword">private</span> Map<Integer, Boolean> indexToEncoded ;</span><br><span class="line"><span class="comment">//自定义的Expander</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">transient</span> Map<Integer, Expander> indexToExpander;</span><br></pre></td></tr></table></figure><blockquote><p><code>Contract也</code>是feign的一个扩展点,一个优秀组件的架构通常是具有很强的扩展性,feign的架构本身很简单,设计的扩展点也很简单方便,所以受到spring的青睐,将其集成到spring cloud中。spring cloud就是通过<code>Contract</code>的扩展,实现使用springMVC的注解接入feign。feign自己还实现了使用jaxrs注解接入feign。</p></blockquote><h2 id="初始化总结"><a href="#初始化总结" class="headerlink" title="初始化总结"></a>初始化总结</h2><p>上文已经完成了feign初始化结构为动态代理的整个过程,简单的捋一遍:</p><ol><li>初始化<code>Feign.Builder</code>传入参数,构造<code>ReflectiveFeign</code></li><li><code>ReflectiveFeign</code>通过内部类<code>ParseHandlersByName</code>的<code>Contract</code>属性,解析接口生成<code>MethodMetadata</code></li><li><code>ParseHandlersByName</code>根据<code>MethodMetadata</code>生成<code>RequestTemplate</code>工厂</li><li><code>ParseHandlersByName</code>创建<code>SynchronousMethodHandler</code>,传入<code>MethodMetadata</code>、<code>RequestTemplate</code>工厂和<code>Feign.Builder</code>相关参数</li><li><code>ReflectiveFeign</code>创建<code>FeignInvocationHandler</code>,传入参数<code>SynchronousMethodHandler</code>,绑定<code>DefaultMethodHandler</code><br>6.<code>ReflectiveFeign</code>根据<code>FeignInvocationHandler</code>创建<code>Proxy</code></li></ol><p>关键的几个类是:</p><ul><li><code>ReflectiveFeign</code> 初始化入口</li><li><code>FeignInvocationHandler</code> 实现动态代理的<code>InvocHandler</code></li><li><code>SynchronousMethodHandler</code> 方法处理器,方法调用处理器</li><li><code>MethodMetadata</code> 方法元数据</li></ul><h2 id="接口调用"><a href="#接口调用" class="headerlink" title="接口调用"></a>接口调用</h2><p>为方便理解,分析完feign源码后,我将feign执行过程分成三层,如下图:</p><p><img src="/2018/06/13/feign-source-analysis/execute.png" alt=""></p><p>三层分别为:</p><ul><li>代理层 动态代理调用层</li><li>转换层 方法转http请求,解码http响应</li><li>网络层 http请求发送</li></ul><p>java动态代理接口方法调用,会调用到<code>InvocaHandler</code>的invoke方法,feign里面实现类是<code>FeignInvocationHandler</code>,invoke代码如下:</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><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map<Method, MethodHandler> dispatch;</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object proxy, Method method, Object[] args)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> 。。。</span><br><span class="line"> <span class="keyword">return</span> dispatch.get(method).invoke(args);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>根据方法找到<code>MethodHandler</code>,除接口的<code>default</code>方法外,找到的是<code>SynchronousMethodHandler</code>对象,然后调用<code>SynchronousMethodHandlerd.invoke</code>方法:</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><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> Object <span class="title">invoke</span><span class="params">(Object[] argv)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> <span class="comment">//buildTemplateFromArgs是RequestTemplate工程对象,根据方法参数创建RequestTemplate</span></span><br><span class="line"> RequestTemplate template = buildTemplateFromArgs.create(argv);</span><br><span class="line"> <span class="comment">//重试设置</span></span><br><span class="line"> Retryer retryer = <span class="keyword">this</span>.retryer.clone();</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//执行和解码</span></span><br><span class="line"> <span class="keyword">return</span> executeAndDecode(template);</span><br><span class="line"> } <span class="keyword">catch</span> (RetryableException e) {</span><br><span class="line"> retryer.continueOrPropagate(e);</span><br><span class="line"> 。。。</span><br><span class="line"> <span class="keyword">continue</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function">Object <span class="title">executeAndDecode</span><span class="params">(RequestTemplate template)</span> <span class="keyword">throws</span> Throwable </span>{</span><br><span class="line"> <span class="comment">//RequestTemplate转换为Request</span></span><br><span class="line"> Request request = targetRequest(template)</span><br><span class="line"> 。。。</span><br><span class="line"> Response response;</span><br><span class="line"> <span class="keyword">long</span> start = System.nanoTime();</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> response = client.execute(request, options);</span><br><span class="line"> response.toBuilder().request(request).build();</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> 。。。</span><br><span class="line"> <span class="keyword">throw</span> errorExecuting(request, e);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">long</span> elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">boolean</span> shouldClose = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> 。。。</span><br><span class="line"> <span class="keyword">if</span> (Response.class == metadata.returnType()) {</span><br><span class="line"> <span class="comment">//如果接口方法返回的是Response类</span></span><br><span class="line"> <span class="keyword">if</span> (response.body() == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//body为空,直接返回</span></span><br><span class="line"> <span class="keyword">return</span> response;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (response.body().length() == <span class="keyword">null</span> ||</span><br><span class="line"> response.body().length() > MAX_RESPONSE_BUFFER_SIZE) {</span><br><span class="line"> <span class="comment">//body不为空,且length>最大缓存值,返回response,但是不能关闭response</span></span><br><span class="line"> shouldClose = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">return</span> response;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">// 读取body字节数组,返回response</span></span><br><span class="line"> <span class="keyword">byte</span>[] bodyData = Util.toByteArray(response.body().asInputStream());</span><br><span class="line"> <span class="keyword">return</span> response.toBuilder().body(bodyData).build();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (response.status() >= <span class="number">200</span> && response.status() < <span class="number">300</span>) {</span><br><span class="line"> <span class="comment">//响应成功</span></span><br><span class="line"> <span class="keyword">if</span> (<span class="keyword">void</span>.class == metadata.returnType()) {</span><br><span class="line"> <span class="comment">//接口返回void</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//解码response,直接调用decoder解码</span></span><br><span class="line"> Object result = decode(response);</span><br><span class="line"> shouldClose = closeAfterDecode;</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (decode404 && response.status() == <span class="number">404</span> && <span class="keyword">void</span>.class != metadata.returnType()) {</span><br><span class="line"> <span class="comment">//404解析</span></span><br><span class="line"> Object result = decode(response);</span><br><span class="line"> shouldClose = closeAfterDecode;</span><br><span class="line"> <span class="keyword">return</span> result;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//其他返回码,使用errorDecoder解析,抛出异常</span></span><br><span class="line"> <span class="keyword">throw</span> errorDecoder.decode(metadata.configKey(), response);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (IOException e) {</span><br><span class="line"> <span class="keyword">throw</span> errorReading(request, response, e);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">//是否需要关闭response,根据Feign.Builder 参数设置是否要关闭流</span></span><br><span class="line"> <span class="keyword">if</span> (shouldClose) {</span><br><span class="line"> ensureClosed(response.body());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>过程比较简单,生成<code>RquestTemplate</code> -> 转换为<code>Request</code> -> <code>client</code>发请求 -> <code>Decoder</code>解析<code>Response</code></p><h2 id="RquestTemplate构建过程"><a href="#RquestTemplate构建过程" class="headerlink" title="RquestTemplate构建过程"></a><code>RquestTemplate</code>构建过程</h2><p>先看看<code>RequestTemplate</code>的结构:<br><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><span class="line"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> <span class="keyword">long</span> serialVersionUID = <span class="number">1L</span>;</span><br><span class="line"><span class="comment">//请求参数 ?后面的name=value</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Map<String, Collection<String>> queries ;</span><br><span class="line"><span class="comment">//请求头</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> Map<String, Collection<String>> headers ;</span><br><span class="line"><span class="comment">//请求方法 GET/POST等</span></span><br><span class="line"> <span class="keyword">private</span> String method;</span><br><span class="line"><span class="comment">//请求路径</span></span><br><span class="line"> <span class="keyword">private</span> StringBuilder url = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"><span class="comment">//字符集</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">transient</span> Charset charset;</span><br><span class="line"><span class="comment">//请求体</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">byte</span>[] body;</span><br><span class="line"><span class="comment">//@Body("%7B\"user_name\": \"{user_name}\", \"password\": \"{password}\"%7D")注解的模板</span></span><br><span class="line"> <span class="keyword">private</span> String bodyTemplate;</span><br><span class="line"><span class="comment">//是否decode削减,将"%2F"反转为"/"</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> decodeSlash = <span class="keyword">true</span>;</span><br><span class="line"><span class="comment">//集合格式化,分隔符</span></span><br><span class="line"> <span class="keyword">private</span> CollectionFormat collectionFormat = CollectionFormat.EXPLODED;</span><br></pre></td></tr></table></figure></p><p>在<code>SynchronousMethodHandler.invoke</code>方法中生成<code>RequestTemplate</code><br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//buildTemplateFromArgs是RequestTemplate.Factory实现类</span></span><br><span class="line">RequestTemplate template = buildTemplateFromArgs.create(argv);</span><br></pre></td></tr></table></figure></p><p><code>RequestTemplate.Factory</code>有三个实现类:</p><ul><li><code>BuildTemplateByResolvingArgs</code> <code>RequestTemplate</code>工厂</li><li><code>BuildEncodedTemplateFromArgs</code> <code>BuildTemplateByResolvingArgs</code>的子类 重载<code>resolve</code>方法,解析form表单请求</li><li><code>BuildFormEncodedTemplateFromArgs</code> <code>BuildTemplateByResolvingArgs</code>的子类,重载<code>resolve</code>方法,解析body请求</li></ul><p><code>BuildTemplateByResolvingArgs</code>创建<code>RequestTemplate</code>的<code>create</code>方法:<br><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><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//BuildTemplateByResolvingArgs实现RequestTemplate.Factory的方法</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> RequestTemplate <span class="title">create</span><span class="params">(Object[] argv)</span> </span>{</span><br><span class="line"> RequestTemplate mutable = <span class="keyword">new</span> RequestTemplate(metadata.template());</span><br><span class="line"> <span class="keyword">if</span> (metadata.urlIndex() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//插入接口方法参数中的URI</span></span><br><span class="line"> <span class="keyword">int</span> urlIndex = metadata.urlIndex();</span><br><span class="line"> mutable.insert(<span class="number">0</span>, String.valueOf(argv[urlIndex]));</span><br><span class="line"> }</span><br><span class="line"> Map<String, Object> varBuilder = <span class="keyword">new</span> LinkedHashMap<String, Object>();</span><br><span class="line"> <span class="comment">//方法参数位置和请求定义的参数名称的map</span></span><br><span class="line"> <span class="keyword">for</span> (Entry<Integer, Collection<String>> entry : metadata.indexToName().entrySet()) {</span><br><span class="line"> <span class="comment">//将方法参数值和定义的请求参数进行映射,varBuilder</span></span><br><span class="line"> <span class="keyword">int</span> i = entry.getKey();</span><br><span class="line"> Object value = argv[entry.getKey()];</span><br><span class="line"> <span class="keyword">if</span> (value != <span class="keyword">null</span>) { <span class="comment">// Null values are skipped.</span></span><br><span class="line"> <span class="keyword">if</span> (indexToExpander.containsKey(i)) {</span><br><span class="line"> value = expandElements(indexToExpander.get(i), value);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (String name : entry.getValue()) {</span><br><span class="line"> varBuilder.put(name, value);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//解析RequestTemplate</span></span><br><span class="line"> RequestTemplate template = resolve(argv, mutable, varBuilder);</span><br><span class="line"> <span class="comment">//解析queryMap,这块代码有些奇怪,为什么单独把queryMap放在这里解析,而不是在resolve方法中,或者在RequestTemplate中</span></span><br><span class="line"> <span class="keyword">if</span> (metadata.queryMapIndex() != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">// add query map parameters after initial resolve so that they take</span></span><br><span class="line"> <span class="comment">// precedence over any predefined values</span></span><br><span class="line"> Object value = argv[metadata.queryMapIndex()];</span><br><span class="line"> Map<String, Object> queryMap = toQueryMap(value);</span><br><span class="line"> template = addQueryMapQueryParameters(queryMap, template);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//解析headerMap定义的参数</span></span><br><span class="line"> <span class="keyword">if</span> (metadata.headerMapIndex() != <span class="keyword">null</span>) {</span><br><span class="line"> template = addHeaderMapHeaders((Map<String, Object>) argv[metadata.headerMapIndex()], template);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="keyword">return</span> template;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//BuildTemplateByResolvingArgs</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> RequestTemplate <span class="title">resolve</span><span class="params">(Object[] argv, RequestTemplate mutable,</span></span></span><br><span class="line"><span class="function"><span class="params"> Map<String, Object> variables)</span> </span>{</span><br><span class="line"> <span class="comment">// 根据需要进行urlEncode参数</span></span><br><span class="line"> Map<String, Boolean> variableToEncoded = <span class="keyword">new</span> LinkedHashMap<String, Boolean>();</span><br><span class="line"> <span class="keyword">for</span> (Entry<Integer, Boolean> entry : metadata.indexToEncoded().entrySet()) {</span><br><span class="line"> Collection<String> names = metadata.indexToName().get(entry.getKey());</span><br><span class="line"> <span class="keyword">for</span> (String name : names) {</span><br><span class="line"> variableToEncoded.put(name, entry.getValue());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//解析参数</span></span><br><span class="line"> <span class="keyword">return</span> mutable.resolve(variables, variableToEncoded);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//BuildEncodedTemplateFromArgs</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> RequestTemplate <span class="title">resolve</span><span class="params">(Object[] argv, RequestTemplate mutable,</span></span></span><br><span class="line"><span class="function"><span class="params"> Map<String, Object> variables)</span> </span>{</span><br><span class="line"> Object body = argv[metadata.bodyIndex()];</span><br><span class="line"> checkArgument(body != <span class="keyword">null</span>, <span class="string">"Body parameter %s was null"</span>, metadata.bodyIndex());</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//编码并设置RequestTemplate的body</span></span><br><span class="line"> encoder.encode(body, metadata.bodyType(), mutable);</span><br><span class="line"> } <span class="keyword">catch</span> (EncodeException e) {</span><br><span class="line"> <span class="keyword">throw</span> e;</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> EncodeException(e.getMessage(), e);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.resolve(argv, mutable, variables);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//BuildFormEncodedTemplateFromArgs</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> RequestTemplate <span class="title">resolve</span><span class="params">(Object[] argv, RequestTemplate mutable,</span></span></span><br><span class="line"><span class="function"><span class="params"> Map<String, Object> variables)</span> </span>{</span><br><span class="line"> <span class="comment">//构造form参数,为HashMap</span></span><br><span class="line"> Map<String, Object> formVariables = <span class="keyword">new</span> LinkedHashMap<String, Object>();</span><br><span class="line"> <span class="keyword">for</span> (Entry<String, Object> entry : variables.entrySet()) {</span><br><span class="line"> <span class="keyword">if</span> (metadata.formParams().contains(entry.getKey())) {</span><br><span class="line"> formVariables.put(entry.getKey(), entry.getValue());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//编码并设置RequestTemplate的body,</span></span><br><span class="line"> encoder.encode(formVariables, Encoder.MAP_STRING_WILDCARD, mutable);</span><br><span class="line"> } <span class="keyword">catch</span> (EncodeException e) {</span><br><span class="line"> <span class="keyword">throw</span> e;</span><br><span class="line"> } <span class="keyword">catch</span> (RuntimeException e) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> EncodeException(e.getMessage(), e);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//调用父类的resolve</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">super</span>.resolve(argv, mutable, variables);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>RequestTemplate</code>解析参数的方法:<br><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><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><span class="line"><span class="comment">//RequestTemplate解析参数的方法</span></span><br><span class="line"><span class="function">RequestTemplate <span class="title">resolve</span><span class="params">(Map<String, ?> unencoded, Map<String, Boolean> alreadyEncoded)</span> </span>{</span><br><span class="line"> <span class="comment">//替换请求Query中的参数</span></span><br><span class="line"> replaceQueryValues(unencoded, alreadyEncoded);</span><br><span class="line"> Map<String, String> encoded = <span class="keyword">new</span> LinkedHashMap<String, String>();</span><br><span class="line"> <span class="keyword">for</span> (Entry<String, ?> entry : unencoded.entrySet()) {</span><br><span class="line"> <span class="keyword">final</span> String key = entry.getKey();</span><br><span class="line"> <span class="keyword">final</span> Object objectValue = entry.getValue();</span><br><span class="line"> String encodedValue = encodeValueIfNotEncoded(key, objectValue, alreadyEncoded);</span><br><span class="line"> encoded.put(key, encodedValue);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//解析Url,替换path中的参数</span></span><br><span class="line"> String resolvedUrl = expand(url.toString(), encoded).replace(<span class="string">"+"</span>, <span class="string">"%20"</span>);</span><br><span class="line"> <span class="keyword">if</span> (decodeSlash) {</span><br><span class="line"> resolvedUrl = resolvedUrl.replace(<span class="string">"%2F"</span>, <span class="string">"/"</span>);</span><br><span class="line"> }</span><br><span class="line"> url = <span class="keyword">new</span> StringBuilder(resolvedUrl);</span><br><span class="line"> <span class="comment">//解析http请求的header</span></span><br><span class="line"> Map<String, Collection<String>> resolvedHeaders = <span class="keyword">new</span> LinkedHashMap<String, Collection<String>>();</span><br><span class="line"> <span class="keyword">for</span> (String field : headers.keySet()) {</span><br><span class="line"> Collection<String> resolvedValues = <span class="keyword">new</span> ArrayList<String>();</span><br><span class="line"> <span class="keyword">for</span> (String value : valuesOrEmpty(headers, field)) {</span><br><span class="line"> String resolved = expand(value, unencoded);</span><br><span class="line"> resolvedValues.add(resolved);</span><br><span class="line"> }</span><br><span class="line"> resolvedHeaders.put(field, resolvedValues);</span><br><span class="line"> }</span><br><span class="line"> headers.clear();</span><br><span class="line"> headers.putAll(resolvedHeaders);</span><br><span class="line"> <span class="comment">//处理bodyTemplate</span></span><br><span class="line"> <span class="keyword">if</span> (bodyTemplate != <span class="keyword">null</span>) {</span><br><span class="line"> body(urlDecode(expand(bodyTemplate, encoded)));</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">this</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//工具方法,将含有{varName}的字符串模板中的变量名用变量值替换</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">expand</span><span class="params">(String template, Map<String, ?> variables)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (checkNotNull(template, <span class="string">"template"</span>).length() < <span class="number">3</span>) {</span><br><span class="line"> <span class="keyword">return</span> template;</span><br><span class="line"> }</span><br><span class="line"> checkNotNull(variables, <span class="string">"variables for %s"</span>, template);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">boolean</span> inVar = <span class="keyword">false</span>;</span><br><span class="line"> StringBuilder var = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"> StringBuilder builder = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">char</span> c : template.toCharArray()) {</span><br><span class="line"> <span class="keyword">switch</span> (c) {</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'{'</span>:</span><br><span class="line"> <span class="keyword">if</span> (inVar) {</span><br><span class="line"> <span class="comment">// '{{' is an escape: write the brace and don't interpret as a variable</span></span><br><span class="line"> builder.append(<span class="string">"{"</span>);</span><br><span class="line"> inVar = <span class="keyword">false</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> inVar = <span class="keyword">true</span>;</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> <span class="string">'}'</span>:</span><br><span class="line"> <span class="keyword">if</span> (!inVar) { <span class="comment">// then write the brace literally</span></span><br><span class="line"> builder.append(<span class="string">'}'</span>);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> }</span><br><span class="line"> inVar = <span class="keyword">false</span>;</span><br><span class="line"> String key = var.toString();</span><br><span class="line"> Object value = variables.get(var.toString());</span><br><span class="line"> <span class="keyword">if</span> (value != <span class="keyword">null</span>) {</span><br><span class="line"> builder.append(value);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> builder.append(<span class="string">'{'</span>).append(key).append(<span class="string">'}'</span>);</span><br><span class="line"> }</span><br><span class="line"> var = <span class="keyword">new</span> StringBuilder();</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">if</span> (inVar) {</span><br><span class="line"> var.append(c);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> builder.append(c);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> builder.toString();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><h2 id="RquestTemplate转换Request"><a href="#RquestTemplate转换Request" class="headerlink" title="RquestTemplate转换Request"></a><code>RquestTemplate</code>转换<code>Request</code></h2><p>先来看看<code>Request</code>的结构,完整的http请求信息的定义:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> String method;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> String url;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Map<String, Collection<String>> headers;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">byte</span>[] body;</span><br><span class="line"><span class="keyword">private</span> <span class="keyword">final</span> Charset charset;</span><br></pre></td></tr></table></figure><p><code>SynchronousMethodHandler</code>的<code>targetRequest</code>方法将<code>RequestTemplate</code>转换为<code>Request</code><br><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><span class="line"><span class="function">Request <span class="title">targetRequest</span><span class="params">(RequestTemplate template)</span> </span>{</span><br><span class="line"> <span class="comment">//先应用所用拦截器,拦截器是在Feign.Builder中传入的,拦截器可以修改RequestTemplate信息</span></span><br><span class="line"> <span class="keyword">for</span> (RequestInterceptor interceptor : requestInterceptors) {</span><br><span class="line"> interceptor.apply(template);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//调用Target的apply方法,默认Target是HardCodedTarget</span></span><br><span class="line"> <span class="keyword">return</span> target.apply(<span class="keyword">new</span> RequestTemplate(template));</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这块先应用所有拦截器,然后target的<code>apply</code>方法。拦截器和target都是扩展点,拦截器可以在构造好<code>RequestTemplate</code>后和发请求前修改请求信息,target默认使用<code>HardCodedTarget</code>直接发请求,feign还提供了<code>LoadBalancingTarget</code>,适配Ribbon来发请求,实现客户端的负载均衡。 </p><p>创建过程:<br><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></pre></td><td class="code"><pre><span class="line"> <span class="comment">//HardCodedTarget的apply方法</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Request <span class="title">apply</span><span class="params">(RequestTemplate input)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (input.url().indexOf(<span class="string">"http"</span>) != <span class="number">0</span>) {</span><br><span class="line"> input.insert(<span class="number">0</span>, url());</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//调用RequestTemplate的request方法</span></span><br><span class="line"> <span class="keyword">return</span> input.request();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//RequestTemplate的request方法</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> Request <span class="title">request</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//安全拷贝所有header</span></span><br><span class="line"> Map<String, Collection<String>> safeCopy = <span class="keyword">new</span> LinkedHashMap<String, Collection<String>>();</span><br><span class="line"> safeCopy.putAll(headers);</span><br><span class="line"> <span class="comment">//调用Request的create静态方法</span></span><br><span class="line"> <span class="keyword">return</span> Request.create(</span><br><span class="line"> method, url + queryLine(),</span><br><span class="line"> Collections.unmodifiableMap(safeCopy),</span><br><span class="line"> body, charset</span><br><span class="line"> );</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//Request的create方法</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> Request <span class="title">create</span><span class="params">(String method, String url, Map<String, Collection<String>> headers,</span></span></span><br><span class="line"><span class="function"><span class="params"> <span class="keyword">byte</span>[] body, Charset charset)</span> </span>{</span><br><span class="line"> <span class="comment">//new 对象</span></span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Request(method, url, headers, body, charset);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>从代码上可以看到,<code>RequestTemplate</code>基本上直接转为<code>Request</code>,没有做什么逻辑操作。对比下<code>LoadBalancingTarget</code>:</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><span class="line"><span class="function"><span class="keyword">public</span> Request <span class="title">apply</span><span class="params">(RequestTemplate input)</span> </span>{</span><br><span class="line"> <span class="comment">//选取一个Server,lb是Ribbon的AbstractLoadBalancer类</span></span><br><span class="line"> Server currentServer = lb.chooseServer(<span class="keyword">null</span>);</span><br><span class="line"> <span class="comment">//生成url</span></span><br><span class="line"> String url = format(<span class="string">"%s://%s%s"</span>, scheme, currentServer.getHostPort(), path);</span><br><span class="line"> input.insert(<span class="number">0</span>, url);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//生成Request</span></span><br><span class="line"> <span class="keyword">return</span> input.request();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> lb.getLoadBalancerStats().incrementNumRequests(currentServer);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>可以看到,非常简单的几行代码,只要修改请求的url就能实现客户端负载均衡。</p><h2 id="http请求发送"><a href="#http请求发送" class="headerlink" title="http请求发送"></a>http请求发送</h2><p><code>SynchronousMethodHandler</code>中构造好<code>Request</code>后,直接调用client的<code>execute</code>方法发送请求:</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">response = client.execute(request, options);</span><br></pre></td></tr></table></figure><p>client是一个<code>Client</code>接口,默认实现类是<code>Client.Default</code>,使用java api中的<code>HttpURLConnection</code>发送http请求。feign还实现了:</p><ul><li><code>ApacheHttpClient</code></li><li><code>OkHttpClient</code></li><li><code>RibbonClient</code><br>使用<code>RibbonClient</code>跟使用<code>LoadBalancingTarget</code>作用都是实现客户端负载均衡,<code>RibbonClient</code>实现稍微复杂些。</li></ul><h2 id="接口调用过程总结"><a href="#接口调用过程总结" class="headerlink" title="接口调用过程总结"></a>接口调用过程总结</h2><p>我们再将接口调用过程捋一遍:</p><p>1、接口的动态代理<code>Proxy</code>调用接口方法会执行的<code>FeignInvocationHandler</code><br>2、<code>FeignInvocationHandler</code>通过方法签名在属性<code>Map<Method, MethodHandler> dispatch</code>中找到<code>SynchronousMethodHandler</code>,调用<code>invoke</code>方法<br>3、<code>SynchronousMethodHandler</code>的<code>invoke</code>方法根据传入的方法参数,通过自身属性工厂对象<code>RequestTemplate.Factory</code>创建<code>RequestTemplate</code>,工厂里面会用根据需要进行<code>Encode</code><br>4、<code>SynchronousMethodHandler</code>遍历自身属性<code>RequestInterceptor</code>列表,对<code>RequestTemplate</code>进行改造<br>4、<code>SynchronousMethodHandler</code>调用自身<code>Target</code>属性的<code>apply</code>方法,将<code>RequestTemplate</code>转换为<code>Request</code>对象<br>5、<code>SynchronousMethodHandler</code>调用自身<code>Client</code>的<code>execute</code>方法,传入<code>Request</code>对象<br>6、<code>Client</code>将<code>Request</code>转换为<code>http</code>请求,发送后将http响应转换为<code>Response</code>对象<br>7、<code>SynchronousMethodHandler</code>调用<code>Decoder</code>的方法对<code>Response</code>对象解码后返回<br>8、返回的对象最后返回到<code>Proxy</code></p><p>时序图如下:<br><img src="/2018/06/13/feign-source-analysis/execute-sequence.png" alt=""></p><h2 id="feign扩展点总结"><a href="#feign扩展点总结" class="headerlink" title="feign扩展点总结"></a>feign扩展点总结</h2><p>前文分析源代码时,已经提到了feign的扩展点,最后我们再将feign的主要扩展点进行总结一下:</p><ul><li><p><code>Contract</code> 契约<br><code>Contract</code>的作用是解析接口方法,生成Rest定义。feign默认使用自己的定义的注解,还提供了</p><ul><li><code>JAXRSContract</code> javax.ws.rs注解接口实现<br><code>SpringContract</code>是spring cloud提供SpringMVC注解实现方式。</li></ul></li><li><p><code>InvocationHandler</code> 动态代理handler<br>通过<code>InvocationHandlerFactory</code>注入到<code>Feign.Builder</code>中,feign提供了Hystrix的扩展,实现Hystrix接入</p></li><li><p><code>Encoder</code> 请求body编码器<br>feign已经提供扩展包含:</p><ul><li>默认编码器,只能处理String和byte[]</li><li>json编码器<code>GsonEncoder</code>、<code>JacksonEncoder</code></li><li>XML编码器<code>JAXBEncoder</code></li></ul></li><li><p><code>Decoder</code> http响应解码器<br>最基本的有:</p><ul><li>json解码器 <code>GsonDecoder</code>、<code>JacksonDecoder</code></li><li>XML解码器 <code>JAXBDecoder</code></li><li>Stream流解码器 <code>StreamDecoder</code></li></ul></li><li><p><code>Target</code> 请求转换器<br>feign提供的实现有:</p><ul><li><code>HardCodedTarget</code> 默认<code>Target</code>,不做任何处理。</li><li><code>LoadBalancingTarget</code> 使用Ribbon进行客户端路由</li></ul></li><li><p><code>Client</code> 发送http请求的客户端<br>feign提供的<code>Client</code>实现有:</p><ul><li><code>Client.Default</code> 默认实现,使用java api的<code>HttpClientConnection</code>发送http请求</li><li><code>ApacheHttpClient</code> 使用apache的Http客户端发送请求</li><li><code>OkHttpClient</code> 使用OKHttp客户端发送请求</li><li><code>RibbonClient</code> 使用Ribbon进行客户端路由</li></ul></li><li><p><code>RequestInterceptor</code> 请求拦截器<br>调用客户端发请求前,修改<code>RequestTemplate</code>,比如为所有请求添加Header就可以用拦截器实现。</p></li><li><p><code>Retryer</code> 重试策略<br>默认的策略是<code>Retryer.Default</code>,包含<code>3</code>个参数:间隔、最大间隔和重试次数,第一次失败重试前会sleep输入的间隔时间的,后面每次重试sleep时间是前一次的<code>1.5</code>倍,超过最大时间或者最大重试次数就失败</p></li></ul>]]></content>
<summary type="html">
<h2 id="Feign介绍"><a href="#Feign介绍" class="headerlink" title="Feign介绍"></a>Feign介绍</h2><p><a href="https://github.com/OpenFeign/feign/" target="_blank" rel="noopener">Feign</a>是一款java的Restful客户端组件,Feign使得 Java HTTP 客户端编写更方便。Feign 灵感来源于<a href="https://github.com/square/retrofit" target="_blank" rel="noopener">Retrofit</a>, <a href="https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html" target="_blank" rel="noopener">JAXRS-2.0</a>和<a href="http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html" target="_blank" rel="noopener">WebSocket</a>。feign在github上有近3K个star,是一款相当优秀的开源组件,虽然相比Retrofit的近30K个star,逊色了太多,但是spring cloud集成了feign,使得feign在java生态中比Retrofit使用的更加广泛。</p>
<p>feign的基本原理是在接口方法上加注解,定义rest请求,构造出接口的动态代理对象,然后通过调用接口方法就可以发送http请求,并且自动解析http响应为方法返回值,极大的简化了客户端调用rest api的代码。官网的示例如下:</p>
</summary>
<category term="spring" scheme="http://along101.site/categories/spring/"/>
<category term="java" scheme="http://along101.site/tags/java/"/>
<category term="rest" scheme="http://along101.site/tags/rest/"/>
<category term="feign" scheme="http://along101.site/tags/feign/"/>
</entry>
<entry>
<title>微服务动态配置组件netflix archaius</title>
<link href="http://along101.site/2018/05/14/netflix-archaius/"/>
<id>http://along101.site/2018/05/14/netflix-archaius/</id>
<published>2018-05-14T12:03:13.000Z</published>
<updated>2018-07-20T03:10:04.409Z</updated>
<content type="html"><![CDATA[<h1 id="archaius介绍"><a href="#archaius介绍" class="headerlink" title="archaius介绍"></a>archaius介绍</h1><p>archaius是Netflix公司开源项目之一,基于java的配置管理类库,主要用于多配置存储的动态获取。主要功能是对apache common configuration类库的扩展。在云平台开发中可以将其用作分布式配置管理依赖构件。同时,它有如下一些特性:</p><ul><li>动态获取属性</li><li>高效和线程安全的配置操作</li><li>配置改变时提供回调机制</li><li>可以通过jmx操作配置</li><li>复合配置</li></ul><a id="more"></a><p>官网给出的结构图:<br><img src="/2018/05/14/netflix-archaius/archaius.png" alt=""></p><h1 id="适用场景"><a href="#适用场景" class="headerlink" title="适用场景"></a>适用场景</h1><p>对于传统的单体应用,properties等配置文件可以解决配置问题,同时也可以通过maven profile配置来区别各个环境,但在一个几百上千节点的的微服务生态中,如何把每个微服务的配置文件都进行更新,并且很多时候还需要重启服务,是一件无法忍受的事情。<br>对于微服务架构而言,一个通用的配置中心是必不可少的。zookeeper、consul、etcd都可以作为集中的配置中心,但是他们的客户端api不是为配置而编写的,使用到配置场景时,会显得非常的臃肿,Archaius可以与这些配置中心结合,它提供的动态配置api,使用起来非常简单方便。<br>spring cloud的spring-cloud-netflix-core组件中集成了archaius,hystrix使用archaius动态修改各commandKey对应的参数(并发数和超时时间),用简单优雅的代码实现不重启生效配置项。</p><h1 id="快速入门"><a href="#快速入门" class="headerlink" title="快速入门"></a>快速入门</h1><p>在maven工程中加入依赖的配置:<br><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><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.archaius<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>archaius-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.7.4<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"> <span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></p><p>archaius已经发展到2.x版本,但是spring cloud集成的还是0.7.X </p><p>编写示例代码:<br><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><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Main</span> </span>{</span><br><span class="line"> <span class="comment">//获取一个Long型的动态配置项,默认值是1000。</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> DynamicLongProperty timeToWait =</span><br><span class="line"> DynamicPropertyFactory.getInstance().getLongProperty(<span class="string">"lock.waitTime"</span>, <span class="number">1000</span>);</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="comment">//设置回调</span></span><br><span class="line"> timeToWait.addCallback(() -> {</span><br><span class="line"> System.out.println(<span class="string">"timeToWait callback, new value: "</span> + timeToWait.get());</span><br><span class="line"> });</span><br><span class="line"> <span class="comment">//每秒将timeToWait动态配置值打印到控制台,timeToWait.get()会动态的获取最新的配置</span></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">100000</span>; i++) {</span><br><span class="line"> Thread.sleep(<span class="number">1000</span>);</span><br><span class="line"> System.out.println(<span class="string">"timeToWait: "</span> + timeToWait.get());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>archaius默认读取classpath下的config.properties,所以在resources目录下增加config.properties文件,文件中增加配置:</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lock.waitTime=4</span><br></pre></td></tr></table></figure><p>然后运行Main,会在控制台打印:</p><figure class="highlight plain"><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><span class="line">timeToWait: 4</span><br><span class="line">timeToWait: 4</span><br><span class="line">timeToWait: 4</span><br><span class="line">。。。</span><br></pre></td></tr></table></figure><p>让main继续运行,修改config.properties配置文件内容:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">lock.waitTime=5</span><br></pre></td></tr></table></figure></p><p>重新编译工程,等待1分钟左右,控制台会打印:</p><figure class="highlight plain"><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><span class="line">timeToWait: 4</span><br><span class="line">timeToWait callback, new value: 5</span><br><span class="line">timeToWait: 5</span><br><span class="line">timeToWait: 5</span><br><span class="line">。。。</span><br></pre></td></tr></table></figure><p>一分钟等得有点长,在启动main时加入VM参数</p><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">-Darchaius.fixedDelayPollingScheduler.delayMills=2000 -Darchaius.fixedDelayPollingScheduler.initialDelayMills=2000</span><br></pre></td></tr></table></figure><p>只要修改config.properties配置文件,两秒后就能生效。</p><h1 id="api说明"><a href="#api说明" class="headerlink" title="api说明"></a>api说明</h1><h2 id="基本类型动态配置"><a href="#基本类型动态配置" class="headerlink" title="基本类型动态配置"></a>基本类型动态配置</h2><ul><li>DynamicFloatProperty</li><li>DynamicDoubleProperty</li><li>DynamicBooleanProperty</li><li>DynamicStringProperty</li><li>DynamicIntProperty</li><li>DynamicLongProperty</li></ul><p>类图如下:<br><img src="/2018/05/14/netflix-archaius/property.png" alt=""></p><p>基本类型的动态配置继承<code>PropertyWrapper</code>类,实现接口<code>Property</code>,方法说明:</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><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">Property</span><<span class="title">T</span>> </span>{</span><br><span class="line"> <span class="comment">//获取动态配置值</span></span><br><span class="line"> <span class="function">T <span class="title">getValue</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="comment">//获取默认值</span></span><br><span class="line"> <span class="function">T <span class="title">getDefaultValue</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="comment">//获取配置名</span></span><br><span class="line"> <span class="function">String <span class="title">getName</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="comment">//获取修改时间戳</span></span><br><span class="line"> <span class="function"><span class="keyword">long</span> <span class="title">getChangedTimestamp</span><span class="params">()</span></span>;</span><br><span class="line"> <span class="comment">//增加修改回调</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">addCallback</span><span class="params">(Runnable callback)</span></span>;</span><br><span class="line"> <span class="comment">//删除所有回调</span></span><br><span class="line"> <span class="function"><span class="keyword">void</span> <span class="title">removeAllCallbacks</span><span class="params">()</span></span>;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>通过<code>DynamicPropertyFactory</code>的单例获取配置项实例</p><h2 id="扩展类型动态配置"><a href="#扩展类型动态配置" class="headerlink" title="扩展类型动态配置"></a>扩展类型动态配置</h2><ul><li><p>DynamicStringListProperty<br>可以动态配置String list</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//String list</span></span><br><span class="line">DynamicStringListProperty prop = <span class="keyword">new</span> DynamicStringListProperty(<span class="string">"test2"</span>, <span class="string">"0|1"</span>, <span class="string">"\\|"</span>);</span><br><span class="line">List<String> list = prop.get();<span class="comment">//获取包含"0","1"的字符串列表</span></span><br></pre></td></tr></table></figure></li><li><p>DynamicStringMapProperty<br>可以动态配置String Map</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//String map</span></span><br><span class="line">DynamicStringMapProperty prop = <span class="keyword">new</span> DynamicStringMapProperty(<span class="string">"test3"</span>, <span class="string">"key1=1,key2=2,key3=3"</span>);</span><br><span class="line">Map<String, String> map = prop.get();</span><br></pre></td></tr></table></figure></li><li><p>DynamicStringSetProperty<br>可以动态配置String set</p><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">//String set</span></span><br><span class="line">DynamicStringSetProperty prop = <span class="keyword">new</span> DynamicStringSetProperty(<span class="string">"test4"</span>, <span class="string">"a,b,c"</span>);</span><br></pre></td></tr></table></figure></li></ul><p>这几个扩展类型内部有个<code>private DynamicStringProperty delegate;</code>属性,通过解析配置字符串创建需要的类型。</p><h2 id="变更回调"><a href="#变更回调" class="headerlink" title="变更回调"></a>变更回调</h2><p>动态配置项通过<code>addCallback</code>方法增加回调函数:<br><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><span class="line"><span class="comment">//设置回调</span></span><br><span class="line">timeToWait.addCallback(() -> {</span><br><span class="line"> System.out.println(<span class="string">"timeToWait callback, new value: "</span> + timeToWait.get());</span><br><span class="line">});</span><br></pre></td></tr></table></figure></p><h1 id="指定配置源"><a href="#指定配置源" class="headerlink" title="指定配置源"></a>指定配置源</h1><h2 id="指定配置文件名称"><a href="#指定配置文件名称" class="headerlink" title="指定配置文件名称"></a>指定配置文件名称</h2><p>archauis默认配置源是classpath下的config.properties,可以增加VM启动参数<br><code>-Darchaius.configurationSource.defaultFileName=test.properties</code>来修改文件名称</p><h2 id="指定配置url"><a href="#指定配置url" class="headerlink" title="指定配置url"></a>指定配置url</h2><p>通过启动参数:<br><code>-Darchaius.configurationSource.additionalUrls=file:///c:/config.properties,https://raw.githubusercontent.com/along101/spring-boot-test/master/actuator-test/src/main/resources/application.properties</code>指定配置源的url,以逗号分隔。<br>配置多个url相同配置项,后面的配置会覆盖前面的,classpath下配置文件优先级最低。</p><h1 id="spring-boot集成"><a href="#spring-boot集成" class="headerlink" title="spring boot集成"></a>spring boot集成</h1><p>spring bean初始化完成,属性值从配置文件注入之后,如果修改配置文件,属性值是不会修改的。如果我们想在运行过程中动态的获取配置项,我们可以将spring的Environment注入到bean中,在代码逻辑中从Environment中获取配置:<br><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><span class="line"><span class="meta">@Autowired</span></span><br><span class="line"><span class="keyword">private</span> Environment env;</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">someMethod</span><span class="params">()</span></span>{</span><br><span class="line"> ...</span><br><span class="line"> String config = environment.getProperty(<span class="string">"someConfig.name"</span>);</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这样的代码显得有些臃肿,并且性能也不高,因为environment是由多个配置源组合起来的。我们使用archaius动态配置就简单很多:<br><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><span class="line"><span class="keyword">private</span> DynamicStringProperty someConfig=DynamicPropertyFactory.getInstance().getStringProperty(<span class="string">"someConfig.name"</span>, <span class="string">"default"</span>);</span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">someMethod</span><span class="params">()</span></span>{</span><br><span class="line"> ...</span><br><span class="line"> String config = someConfig.get();</span><br><span class="line"> ...</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>如果想参数变更后做些业务操作,archaius非常简单:<br><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><span class="line"><span class="comment">//某个bean的内部</span></span><br><span class="line"><span class="keyword">private</span> DynamicStringProperty someConfig=DynamicPropertyFactory.getInstance().getStringProperty(<span class="string">"someConfig.name"</span>, <span class="string">"default"</span>);</span><br><span class="line"></span><br><span class="line"><span class="meta">@PostConstruct</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">someMethod</span><span class="params">()</span></span>{</span><br><span class="line"> someConfig.addCallback(() -> {</span><br><span class="line"> System.out.println(<span class="string">"new value: "</span> + someConfig.get());</span><br><span class="line"> <span class="comment">//业务代码</span></span><br><span class="line"> });</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>spring boot中如何集成archaius呢? spring-cloud-netflix-core自动配置了archaius,集成spring boot,只要加入如下依赖:<br><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></pre></td><td class="code"><pre><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>org.springframework.cloud<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>spring-cloud-netflix-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>1.2.7.RELEASE<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br><span class="line"><span class="tag"><<span class="name">dependency</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">groupId</span>></span>com.netflix.archaius<span class="tag"></<span class="name">groupId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">artifactId</span>></span>archaius-core<span class="tag"></<span class="name">artifactId</span>></span></span><br><span class="line"> <span class="tag"><<span class="name">version</span>></span>0.7.4<span class="tag"></<span class="name">version</span>></span></span><br><span class="line"><span class="tag"></<span class="name">dependency</span>></span></span><br></pre></td></tr></table></figure></p><p>编写测试类TestApplication验证:<br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> Environment env;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> ArchaiusAutoConfiguration archaiusAutoConfiguration;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> DynamicStringProperty archaiusTest =</span><br><span class="line"> DynamicPropertyFactory.getInstance().getStringProperty(<span class="string">"archaius.test"</span>, <span class="string">"test1"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>{</span><br><span class="line"> SpringApplication.run(TestApplication.class, args);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@PostConstruct</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"env config: "</span> + env.getProperty(<span class="string">"archaius.test"</span>));</span><br><span class="line"> System.out.println(<span class="string">"archaius config: "</span> + archaiusTest.get());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>在spring boot配置文件中加入配置:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">archaius.test=archaius-test</span><br></pre></td></tr></table></figure></p><p>运行测试类TestApplication,会在控制台打印出<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">env config: archaius-test</span><br><span class="line">archaius config: archaius-test</span><br></pre></td></tr></table></figure></p><p>说明archaius从spring 的env中拿到配置了。 </p><p>但是这里有个问题,spring boot本身使用了外部配置,比如集成了apollo,<strong>修改配置,archaius配置项是不生效的</strong>。分析spring-cloud-netflix-core的源代码,里面有个ConfigurableEnvironmentConfiguration继承了apache的AbstractConfiguration,将spring Environment进行了封装,实例化后作为配置源加到archaius的组合配置中,并没有像DynamicURLConfiguration那样进行schedule。 </p><p>我们前面对archaius的源代码进行了详细的分析,可以对此进行改进,让spring更新env时,archaius配置项能感知到。首先增加一个配置源类,实现PolledConfigurationSource接口:<br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf</span>4j</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">SpringEnvConfigurationSource</span> <span class="keyword">implements</span> <span class="title">PolledConfigurationSource</span> </span>{</span><br><span class="line"> <span class="comment">//spring的Environment</span></span><br><span class="line"> <span class="keyword">private</span> ConfigurableEnvironment environment;</span><br><span class="line"> <span class="keyword">private</span> PropertySourcesPropertyResolver resolver;</span><br><span class="line"></span><br><span class="line"> SpringEnvConfigurationSource(ConfigurableEnvironment environment) {</span><br><span class="line"> <span class="keyword">this</span>.environment = environment;</span><br><span class="line"> <span class="keyword">this</span>.resolver = <span class="keyword">new</span> PropertySourcesPropertyResolver(<span class="keyword">this</span>.environment.getPropertySources());</span><br><span class="line"> <span class="keyword">this</span>.resolver.setIgnoreUnresolvableNestedPlaceholders(<span class="keyword">true</span>);</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> PollResult <span class="title">poll</span><span class="params">(<span class="keyword">boolean</span> initial, Object checkPoint)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> log.debug(<span class="string">"archaius config refresh."</span>);</span><br><span class="line"> <span class="comment">//poll是从spring env中拉取配置</span></span><br><span class="line"> <span class="keyword">return</span> PollResult.createFull(getProperties());</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Map<String, Object> <span class="title">getProperties</span><span class="params">()</span> </span>{</span><br><span class="line"> Map<String, Object> properties = <span class="keyword">new</span> LinkedHashMap<>();</span><br><span class="line"> <span class="comment">//spring env 里面也是多个source组合的</span></span><br><span class="line"> <span class="keyword">for</span> (Map.Entry<String, PropertySource<?>> entry : getPropertySources().entrySet()) {</span><br><span class="line"> PropertySource<?> source = entry.getValue();</span><br><span class="line"> <span class="keyword">if</span> (source <span class="keyword">instanceof</span> EnumerablePropertySource) {</span><br><span class="line"> EnumerablePropertySource<?> enumerable = (EnumerablePropertySource<?>) source;</span><br><span class="line"> <span class="keyword">for</span> (String name : enumerable.getPropertyNames()) {</span><br><span class="line"> <span class="keyword">if</span> (!properties.containsKey(name)) {</span><br><span class="line"> properties.put(name, resolver.getProperty(name));</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> properties;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="comment">//PropertySource也可能是组合的,通过递归获取</span></span><br><span class="line"> <span class="keyword">private</span> Map<String, PropertySource<?>> getPropertySources() {</span><br><span class="line"> Map<String, PropertySource<?>> map = <span class="keyword">new</span> LinkedHashMap<String, PropertySource<?>>();</span><br><span class="line"> MutablePropertySources sources = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">if</span> (environment != <span class="keyword">null</span>) {</span><br><span class="line"> sources = environment.getPropertySources();</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> sources = <span class="keyword">new</span> StandardEnvironment().getPropertySources();</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">for</span> (PropertySource<?> source : sources) {</span><br><span class="line"> extract(<span class="string">""</span>, map, source);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> map;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">private</span> <span class="keyword">void</span> <span class="title">extract</span><span class="params">(String root, Map<String, PropertySource<?>> map,</span></span></span><br><span class="line"><span class="function"><span class="params"> PropertySource<?> source)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (source <span class="keyword">instanceof</span> CompositePropertySource) {</span><br><span class="line"> <span class="keyword">for</span> (PropertySource<?> nest : ((CompositePropertySource) source)</span><br><span class="line"> .getPropertySources()) {</span><br><span class="line"> extract(source.getName() + <span class="string">":"</span>, map, nest);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> map.put(root + source.getName(), source);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这段代码是参考spring-cloud-netflix-core中的ConfigurableEnvironmentConfiguration实现的。<br>然后编写自动配置类代码:<br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@ConfigurationProperties</span>(<span class="string">"archaius"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">ArchaiusConfig</span> <span class="keyword">implements</span> <span class="title">EnvironmentAware</span>, <span class="title">InitializingBean</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> String CONFIG_NAME = <span class="string">"springEnv"</span>;</span><br><span class="line"> <span class="meta">@Setter</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> pollDelayMillis = <span class="number">5000</span>;</span><br><span class="line"> <span class="meta">@Setter</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">int</span> poolInitialDelayMillis = <span class="number">5000</span>;</span><br><span class="line"> <span class="meta">@Setter</span></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">boolean</span> pollIgnoreDeletesFromSource = <span class="keyword">false</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> Environment environment;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setEnvironment</span><span class="params">(Environment environment)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.environment = environment;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">afterPropertiesSet</span><span class="params">()</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> SpringEnvConfigurationSource springEnvConfigurationSource = <span class="keyword">new</span> SpringEnvConfigurationSource((ConfigurableEnvironment) <span class="keyword">this</span>.environment);</span><br><span class="line"> <span class="comment">//新建一个PollingScheduler</span></span><br><span class="line"> FixedDelayPollingScheduler scheduler = <span class="keyword">new</span> FixedDelayPollingScheduler(poolInitialDelayMillis, pollDelayMillis, pollIgnoreDeletesFromSource);</span><br><span class="line"> ConcurrentMapConfiguration configuration = <span class="keyword">new</span> ConcurrentCompositeConfiguration();</span><br><span class="line"> <span class="comment">//开始轮询,从SpringEnvConfigurationSource中拉取,archaius内部会比较配置的变化</span></span><br><span class="line"> scheduler.startPolling(springEnvConfigurationSource, configuration);</span><br><span class="line"></span><br><span class="line"> <span class="comment">//初始化archaius,这段代码也是参考spring-cloud-netflix-core的</span></span><br><span class="line"> <span class="keyword">if</span> (ConfigurationManager.isConfigurationInstalled()) {</span><br><span class="line"> AbstractConfiguration installedConfiguration = ConfigurationManager.getConfigInstance();</span><br><span class="line"> <span class="keyword">if</span> (installedConfiguration <span class="keyword">instanceof</span> ConcurrentCompositeConfiguration) {</span><br><span class="line"> ConcurrentCompositeConfiguration configInstance = (ConcurrentCompositeConfiguration) installedConfiguration;</span><br><span class="line"> <span class="keyword">if</span> (configInstance.getConfiguration(CONFIG_NAME) == <span class="keyword">null</span>)</span><br><span class="line"> configInstance.addConfigurationAtFront(configuration, CONFIG_NAME);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> ConcurrentCompositeConfiguration concurrentCompositeConfiguration = <span class="keyword">new</span> ConcurrentCompositeConfiguration();</span><br><span class="line"> concurrentCompositeConfiguration.addConfiguration(configuration, CONFIG_NAME);</span><br><span class="line"> ConfigurationManager.install(concurrentCompositeConfiguration);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>修改启动类进行测试:<br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">TestApplication</span> </span>{</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> ConfigurableEnvironment env;</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Autowired</span></span><br><span class="line"> ArchaiusAutoConfiguration archaiusAutoConfiguration;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">final</span> DynamicStringProperty archaiusTest =</span><br><span class="line"> DynamicPropertyFactory.getInstance().getStringProperty(<span class="string">"archaius.test"</span>, <span class="string">"test1"</span>);</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException </span>{</span><br><span class="line"> SpringApplication.run(TestApplication.class, args);</span><br><span class="line"> Thread.sleep(<span class="number">50000</span> * <span class="number">1000</span>);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@PostConstruct</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">init</span><span class="params">()</span> </span>{</span><br><span class="line"> System.out.println(<span class="string">"env config: "</span> + env.getProperty(<span class="string">"archaius.test"</span>));</span><br><span class="line"> System.out.println(<span class="string">"archaius config: "</span> + archaiusTest.get());</span><br><span class="line"> <span class="comment">//给env中增加一个配置源</span></span><br><span class="line"> MutablePropertySources sources = env.getPropertySources();</span><br><span class="line"> Map<String, Object> config = <span class="keyword">new</span> HashMap<>();</span><br><span class="line"> config.put(<span class="string">"archaius.test"</span>, <span class="string">"map change"</span>);</span><br><span class="line"> <span class="comment">//自定义的配置源加到最前面</span></span><br><span class="line"> sources.addFirst(<span class="keyword">new</span> MapPropertySource(<span class="string">"myMap"</span>, config));</span><br><span class="line"> <span class="keyword">new</span> Thread(<span class="keyword">new</span> Runnable() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">int</span> i = <span class="number">0</span>;</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> Thread.sleep(<span class="number">2</span> * <span class="number">1000</span>);</span><br><span class="line"> <span class="comment">//打印archaius配置项</span></span><br><span class="line"> System.out.println(<span class="string">"archaius config: "</span> + archaiusTest.get());</span><br><span class="line"> i++;</span><br><span class="line"> <span class="comment">//修改配置源</span></span><br><span class="line"> config.put(<span class="string">"archaius.test"</span>, <span class="string">"map change "</span> + i);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }).start();</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>运行结果:<br><figure class="highlight plain"><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><span class="line">env config: archaius-test</span><br><span class="line">archaius config: archaius-test</span><br><span class="line">...</span><br><span class="line">archaius config: map change</span><br><span class="line">archaius config: map change</span><br><span class="line">archaius config: map change 2</span><br><span class="line">archaius config: map change 2</span><br><span class="line">archaius config: map change 2</span><br><span class="line">archaius config: map change 5</span><br><span class="line">archaius config: map change 5</span><br><span class="line">archaius config: map change 7</span><br><span class="line">archaius config: map change 7</span><br></pre></td></tr></table></figure></p><p>可以看到不是每次修改都会生效,轮询时间在ArchaiusConfig中进行的配置。代码地址:<a href="https://github.com/along101/spring-boot-test/tree/5ad5158732ea363aa4fb5b9b965c08977699a9c6/archaius-test" target="_blank" rel="noopener">https://github.com/along101/spring-boot-test/tree/5ad5158732ea363aa4fb5b9b965c08977699a9c6/archaius-test</a></p><p>就这么几十行代码就可以将archaius与spring boot结合起来了。</p><h1 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h1><p>archaius源码不多,重点在配置的初始化和动态变更这块。</p><h2 id="初始化配置"><a href="#初始化配置" class="headerlink" title="初始化配置"></a>初始化配置</h2><p>初始化入口是<code>DynamicPropertyFactory.getInstance()</code>,来看看该方法源码<br><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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> DynamicPropertyFactory <span class="title">getInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (config == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">synchronized</span> (ConfigurationManager.class) {</span><br><span class="line"> <span class="keyword">if</span> (config == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//先初始化AbstractConfiguration</span></span><br><span class="line"> AbstractConfiguration configFromManager = ConfigurationManager.getConfigInstance();</span><br><span class="line"> <span class="keyword">if</span> (configFromManager != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//初始化自己</span></span><br><span class="line"> initWithConfigurationSource(configFromManager);</span><br><span class="line"> initializedWithDefaultConfig = !ConfigurationManager.isConfigurationInstalled();</span><br><span class="line"> logger.info(<span class="string">"DynamicPropertyFactory is initialized with configuration sources: "</span> + configFromManager);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> instance;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>ConfigurationManager</code>的静态代码块中执行</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><span class="line"><span class="keyword">static</span>{</span><br><span class="line"> ...省略部分代码</span><br><span class="line"></span><br><span class="line"> <span class="comment">//设置部署上下文,会初始化environment、datacenter、applicationId、serverId、region等等,不配置的话,就为空</span></span><br><span class="line"> setDeploymentContext(<span class="keyword">new</span> ConfigurationBasedDeploymentContext());</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>经过一层层调用,调用到<code>createDefaultConfigInstance</code>来创建<code>AbstractConfiguration</code>:<br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> AbstractConfiguration <span class="title">createDefaultConfigInstance</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="comment">//新建一个组合配置,配置列表中自带一个ConcurrentMapConfiguration,这个配置是containerConfiguration,容器自己的配置</span></span><br><span class="line"> ConcurrentCompositeConfiguration config = <span class="keyword">new</span> ConcurrentCompositeConfiguration(); </span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//增加一个DynamicURLConfiguration,获取默认配置文件url和配置系统属性archaius.configurationSource.additionalUrls得到的url</span></span><br><span class="line"> <span class="comment">//构造函数最后,开启线程定时从url里面更新配置</span></span><br><span class="line"> DynamicURLConfiguration defaultURLConfig = <span class="keyword">new</span> DynamicURLConfiguration();</span><br><span class="line"> config.addConfiguration(defaultURLConfig, URL_CONFIG_NAME);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> logger.warn(<span class="string">"Failed to create default dynamic configuration"</span>, e);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!Boolean.getBoolean(DISABLE_DEFAULT_SYS_CONFIG)) {</span><br><span class="line"> <span class="comment">//增加SystemConfiguration</span></span><br><span class="line"> SystemConfiguration sysConfig = <span class="keyword">new</span> SystemConfiguration();</span><br><span class="line"> config.addConfiguration(sysConfig, SYS_CONFIG_NAME);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!Boolean.getBoolean(DISABLE_DEFAULT_ENV_CONFIG)) {</span><br><span class="line"> <span class="comment">//增加环境配置</span></span><br><span class="line"> EnvironmentConfiguration envConfig = <span class="keyword">new</span> EnvironmentConfiguration();</span><br><span class="line"> config.addConfiguration(envConfig, ENV_CONFIG_NAME);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//增加app的组合配置</span></span><br><span class="line"> ConcurrentCompositeConfiguration appOverrideConfig = <span class="keyword">new</span> ConcurrentCompositeConfiguration();</span><br><span class="line"> config.addConfiguration(appOverrideConfig, APPLICATION_PROPERTIES);</span><br><span class="line"> config.setContainerConfigurationIndex(config.getIndexOfConfiguration(appOverrideConfig));</span><br><span class="line"> <span class="keyword">return</span> config;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>初始化完后,得到一个组合配置,包含5个配置,顺序为:</p><ul><li>URL_CONFIG_NAME 通过url拉取的配置</li><li>SYS_CONFIG_NAME 系统属性配置,通过<code>System.getProperties()</code>获取的配置</li><li>ENV_CONFIG_NAME 环境配置,通过<code>System.getenv()</code>获取的配置</li><li>containerConfiguration 容器配置,<code>ConfigurationManager.getConfigInstance().setProperty(key,value)</code>设置的配置</li><li>APPLICATION_PROPERTIES 调用<code>configurationManager.loadAppOverrideProperties</code>设置的配置</li></ul><p><img src="/2018/05/14/netflix-archaius/config.png" alt=""></p><p>这样就生成了<code>AbstractConfiguration</code>,保存在<code>ConfigurationManager.instance</code>静态变量上。 </p><p>回到<code>DynamicPropertyFactory</code>代码里面,生成了<code>AbstractConfiguration configFromManager</code>之后,调用方法<code>initWithConfigurationSource</code>初始化:</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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> DynamicPropertyFactory <span class="title">initWithConfigurationSource</span><span class="params">(AbstractConfiguration config)</span> </span>{</span><br><span class="line"></span><br><span class="line"> 。。。</span><br><span class="line"> <span class="comment">//包装config为DynamicPropertySupport</span></span><br><span class="line"> <span class="keyword">return</span> initWithConfigurationSource(<span class="keyword">new</span> ConfigurationBackedDynamicPropertySupportImpl(config));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>后面会设置<code>setDirect</code>,将<code>DynamicPropertySupport</code>注册到<code>DynamicProperty</code>中<br><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><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">setDirect</span><span class="params">(DynamicPropertySupport support)</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (ConfigurationManager.class) {</span><br><span class="line"> config = support;</span><br><span class="line"> DynamicProperty.registerWithDynamicPropertySupport(support);</span><br><span class="line"> initializedWithDefaultConfig = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>DynamicProperty</code>注册<code>DynamicPropertySupport</code>的过程是增加一个<code>DynamicPropertyListener</code>,更新所有的属性,也就是更新他的静态变量<code>ALL_PROPS</code>里面的配置项<br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">initialize</span><span class="params">(DynamicPropertySupport config)</span> </span>{</span><br><span class="line"> dynamicPropertySupportImpl = config;</span><br><span class="line"> config.addConfigurationListener(<span class="keyword">new</span> DynamicPropertyListener());</span><br><span class="line"> updateAllProperties();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>到这里就初始化完了。根据以上源代码的分析,我们画出类图如下:</p><p><img src="/2018/05/14/netflix-archaius/init.png" alt=""></p><p>这里最重要的两个类是</p><ul><li><code>ConfigurationManager</code> 静态变量<code>AbstractConfiguration instance为</code>所有配置项信息</li><li><code>DynamicProperty</code> 静态变量<code>DynamicPropertySupport dynamicPropertySupportImpl</code>用于动态更新,静态变量<code>ConcurrentHashMap<String, DynamicProperty> ALL_PROPS</code>为被使用的动态属性</li></ul><h2 id="获取配置"><a href="#获取配置" class="headerlink" title="获取配置"></a>获取配置</h2><p>通过以下代码获取动态配置:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">DynamicLongProperty timeToWait =</span><br><span class="line"> DynamicPropertyFactory.getInstance().getLongProperty(<span class="string">"lock.waitTime"</span>, <span class="number">1000</span>);</span><br></pre></td></tr></table></figure></p><p>进入<code>getLongProperty</code>方法:</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><span class="line"><span class="function"><span class="keyword">public</span> DynamicLongProperty <span class="title">getLongProperty</span><span class="params">(String propName, <span class="keyword">long</span> defaultValue, <span class="keyword">final</span> Runnable propertyChangeCallback)</span> </span>{</span><br><span class="line"> <span class="comment">//检查初始化</span></span><br><span class="line"> checkAndWarn(propName);</span><br><span class="line"> <span class="comment">//新建一个动态配置</span></span><br><span class="line"> DynamicLongProperty property = <span class="keyword">new</span> DynamicLongProperty(propName, defaultValue);</span><br><span class="line"> <span class="comment">//增加回调</span></span><br><span class="line"> addCallback(propertyChangeCallback, property);</span><br><span class="line"> <span class="keyword">return</span> property;</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p><code>DynamicLongProperty</code>继承了<code>PropertyWrapper</code>,含有属性<code>DynamicProperty prop</code>,先初始化该属性:<br><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">this</span>.prop = DynamicProperty.getInstance(propName);</span><br></pre></td></tr></table></figure></p><p>再往下看:<br><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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> DynamicProperty <span class="title">getInstance</span><span class="params">(String propName)</span> </span>{</span><br><span class="line"> <span class="comment">//dynamicPropertySupportImpl为空,先初始化DynamicPropertyFactory</span></span><br><span class="line"> <span class="keyword">if</span> (dynamicPropertySupportImpl == <span class="keyword">null</span>) {</span><br><span class="line"> DynamicPropertyFactory.getInstance();</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//从ALL_PROPS中获取prop,没有的话,新建,并放到ALL_PROPS中</span></span><br><span class="line"> <span class="comment">//这里没有加同步设置,但是做了个小动作,没有并发问题</span></span><br><span class="line"> DynamicProperty prop = ALL_PROPS.get(propName);</span><br><span class="line"> <span class="keyword">if</span> (prop == <span class="keyword">null</span>) {</span><br><span class="line"> prop = <span class="keyword">new</span> DynamicProperty(propName);</span><br><span class="line"> <span class="comment">//ALL_PROPS是ConcurrentHashMap,putIfAbsent是线程安全的,存在的话返回老的值</span></span><br><span class="line"> DynamicProperty oldProp = ALL_PROPS.putIfAbsent(propName, prop);</span><br><span class="line"> <span class="comment">//oldProp不为null,返回oldProp,解决了并发的问题,副作用是多建了DynamicProperty对象</span></span><br><span class="line"> <span class="keyword">if</span> (oldProp != <span class="keyword">null</span>) {</span><br><span class="line"> prop = oldProp;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> prop;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>再看<code>new DynamicProperty(propName)</code></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></pre></td><td class="code"><pre><span class="line"> <span class="function"><span class="keyword">private</span> <span class="title">DynamicProperty</span><span class="params">(String propName)</span> </span>{</span><br><span class="line"> <span class="comment">//设置属性名称</span></span><br><span class="line"> <span class="keyword">this</span>.propName = propName;</span><br><span class="line"> updateValue();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="comment">//更新属性值</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">boolean</span> <span class="title">updateValue</span><span class="params">()</span> </span>{</span><br><span class="line"> String newValue;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="keyword">if</span> (dynamicPropertySupportImpl != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//从dynamicPropertySupportImpl中获取配置项,这里是包装AbstractConfiguration的,也就是在ConfigurationManager中的instance</span></span><br><span class="line"> newValue = dynamicPropertySupportImpl.getString(propName);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> logger.error(<span class="string">"Unable to update property: "</span> + propName, e);</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> updateValue(newValue);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">updateValue</span><span class="params">(Object newValue)</span> </span>{</span><br><span class="line"> String nv = (newValue == <span class="keyword">null</span>) ? <span class="keyword">null</span> : newValue.toString();</span><br><span class="line"> <span class="keyword">synchronized</span> (lock) {</span><br><span class="line"> <span class="keyword">if</span> ((nv == <span class="keyword">null</span> && stringValue == <span class="keyword">null</span>)</span><br><span class="line"> || (nv != <span class="keyword">null</span> && nv.equals(stringValue))) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//更新stringValue</span></span><br><span class="line"> stringValue = nv;</span><br><span class="line"> <span class="comment">//刷新缓存</span></span><br><span class="line"> cachedStringValue.flush();</span><br><span class="line"> booleanValue.flush();</span><br><span class="line"> integerValue.flush();</span><br><span class="line"> floatValue.flush();</span><br><span class="line"> classValue.flush();</span><br><span class="line"> doubleValue.flush();</span><br><span class="line"> longValue.flush();</span><br><span class="line"> changedTime = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>到这里<code>DynamicLongProperty</code>对象就新建了,里面包含一个<code>DynamicProperty</code>,配置值的更新、回调都是由这个<code>DynamicProperty</code>完成的,他的属性有:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//配置名称</span></span><br><span class="line"><span class="keyword">private</span> String propName;</span><br><span class="line"><span class="comment">//配置的值,原始string</span></span><br><span class="line"><span class="keyword">private</span> String stringValue = <span class="keyword">null</span>;</span><br><span class="line"><span class="comment">//更新时间</span></span><br><span class="line"><span class="keyword">private</span> <span class="keyword">long</span> changedTime;</span><br><span class="line"><span class="comment">//回到函数表</span></span><br><span class="line"><span class="keyword">private</span> CopyOnWriteArraySet<Runnable> callbacks = <span class="keyword">new</span> CopyOnWriteArraySet<Runnable>();</span><br><span class="line"></span><br><span class="line"><span class="comment">//缓存的StringValue</span></span><br><span class="line"><span class="keyword">private</span> CachedValue<String> cachedStringValue = <span class="keyword">new</span> CachedValue<String>() {</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> String <span class="title">parse</span><span class="params">(String rep)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> rep;</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"><span class="comment">//缓存的integerValue</span></span><br><span class="line"><span class="keyword">private</span> CachedValue<Integer> integerValue = <span class="keyword">new</span> CachedValue<Integer>() {</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Integer <span class="title">parse</span><span class="params">(String rep)</span> <span class="keyword">throws</span> NumberFormatException </span>{</span><br><span class="line"> <span class="keyword">return</span> Integer.valueOf(rep);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> CachedValue<Long> longValue = <span class="keyword">new</span> CachedValue<Long>() {</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Long <span class="title">parse</span><span class="params">(String rep)</span> <span class="keyword">throws</span> NumberFormatException </span>{</span><br><span class="line"> <span class="keyword">return</span> Long.valueOf(rep);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> CachedValue<Float> floatValue = <span class="keyword">new</span> CachedValue<Float>() {</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Float <span class="title">parse</span><span class="params">(String rep)</span> <span class="keyword">throws</span> NumberFormatException </span>{</span><br><span class="line"> <span class="keyword">return</span> Float.valueOf(rep);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> CachedValue<Double> doubleValue = <span class="keyword">new</span> CachedValue<Double>() {</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Double <span class="title">parse</span><span class="params">(String rep)</span> <span class="keyword">throws</span> NumberFormatException </span>{</span><br><span class="line"> <span class="keyword">return</span> Double.valueOf(rep);</span><br><span class="line"> }</span><br><span class="line">};</span><br><span class="line"></span><br><span class="line"><span class="keyword">private</span> CachedValue<Class> classValue = <span class="keyword">new</span> CachedValue<Class>() {</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Class <span class="title">parse</span><span class="params">(String rep)</span> <span class="keyword">throws</span> ClassNotFoundException </span>{</span><br><span class="line"> <span class="keyword">return</span> Class.forName(rep);</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>这个<code>CachedValue</code>是<code>DynamicLongProperty</code>的内部类,缓存了配置的实际值,通过解析<code>DynamicLongProperty</code>的stringValue值获取不同类型的配置值。下面我们来分析这一过程。<code>DynamicLongProperty.get()</code>获取动态配置项的值代码如下:<br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">long</span> <span class="title">get</span><span class="params">()</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> prop.getLong(defaultValue).longValue();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>通过内置的prop属性即<code>DynamicProperty</code>获取的配置项,prop中通过属性longValue获取配置项<br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> Long <span class="title">getLong</span><span class="params">(Long defaultValue)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> longValue.getValue(defaultValue);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>前面初始化prop时,会初始化属性<code>CachedValue<Long> longValue</code>:</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></pre></td><td class="code"><pre><span class="line"><span class="keyword">private</span> CachedValue<Long> longValue = <span class="keyword">new</span> CachedValue<Long>() {</span><br><span class="line"> <span class="function"><span class="keyword">protected</span> Long <span class="title">parse</span><span class="params">(String rep)</span> <span class="keyword">throws</span> NumberFormatException </span>{</span><br><span class="line"> <span class="keyword">return</span> Long.valueOf(rep);</span><br><span class="line"> }</span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>longValue是一个<code>CachedValue</code>类型,getValue方法如下:</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><span class="line"><span class="function"><span class="keyword">public</span> T <span class="title">getValue</span><span class="params">()</span> <span class="keyword">throws</span> IllegalArgumentException </span>{</span><br><span class="line"> <span class="comment">// Not quite double-check locking -- since isCached is marked as volatile</span></span><br><span class="line"> <span class="keyword">if</span> (!isCached) {</span><br><span class="line"> <span class="keyword">synchronized</span> (lock) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//如果配置值DynamicProperty.stringValue为null,则没有该配置项,返回null,否则解析stringValue返回</span></span><br><span class="line"> <span class="comment">//这里的parse方法自然是调用longValue的parse方法返回Long值</span></span><br><span class="line"> value = (stringValue == <span class="keyword">null</span>) ? <span class="keyword">null</span> : parse(stringValue);</span><br><span class="line"> exception = <span class="keyword">null</span>;</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> value = <span class="keyword">null</span>;</span><br><span class="line"> exception = <span class="keyword">new</span> IllegalArgumentException(e);</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> isCached = <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (exception != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> exception;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="keyword">return</span> value;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>到这里,通过<code>DynamicLongProperty.getLongProperty</code>获取的配置值就能拿到了。其他的<code>DynamicIntProperty</code>、<code>DynamicStringProperty</code>等配置项的获取与此相同。</p><h2 id="动态更新"><a href="#动态更新" class="headerlink" title="动态更新"></a>动态更新</h2><p>archaius最有价值的特点是能动态更新配置项,这里动态更新的配置项是指前面我们分析初始化是的<code>DynamicURLConfiguration</code>,我们先看看<code>DynamicURLConfiguration</code>的是如何动态更新的,构造函数:</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><span class="line"><span class="function"><span class="keyword">public</span> <span class="title">DynamicURLConfiguration</span><span class="params">()</span> </span>{</span><br><span class="line"> URLConfigurationSource source = <span class="keyword">new</span> URLConfigurationSource();</span><br><span class="line"> <span class="keyword">if</span> (source.getConfigUrls() != <span class="keyword">null</span> && source.getConfigUrls().size() > <span class="number">0</span>) {</span><br><span class="line"> startPolling(source, <span class="keyword">new</span> FixedDelayPollingScheduler());</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的startPolling开始轮询url资源内容,然后更新:</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">startPolling</span><span class="params">(PolledConfigurationSource source, AbstractPollingScheduler scheduler)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.scheduler = scheduler;</span><br><span class="line"> <span class="keyword">this</span>.source = source;</span><br><span class="line"> init(source, scheduler);</span><br><span class="line"> <span class="comment">//然后schedule调度轮询</span></span><br><span class="line"> scheduler.startPolling(source, <span class="keyword">this</span>); </span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">startPolling</span><span class="params">(<span class="keyword">final</span> PolledConfigurationSource source, <span class="keyword">final</span> Configuration config)</span> </span>{</span><br><span class="line"> <span class="comment">//先初始化</span></span><br><span class="line"> initialLoad(source, config);</span><br><span class="line"> Runnable r = getPollingRunnable(source, config);</span><br><span class="line"> schedule(r);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">schedule</span><span class="params">(Runnable runnable)</span> </span>{</span><br><span class="line"><span class="comment">//线程池调度</span></span><br><span class="line"> executor = Executors.newScheduledThreadPool(<span class="number">1</span>, <span class="keyword">new</span> ThreadFactory() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> Thread <span class="title">newThread</span><span class="params">(Runnable r)</span> </span>{</span><br><span class="line"> Thread t = <span class="keyword">new</span> Thread(r, <span class="string">"pollingConfigurationSource"</span>);</span><br><span class="line"> t.setDaemon(<span class="keyword">true</span>);</span><br><span class="line"> <span class="keyword">return</span> t;</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> executor.scheduleWithFixedDelay(runnable, initialDelayMillis, delayMillis, TimeUnit.MILLISECONDS);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">protected</span> Runnable <span class="title">getPollingRunnable</span><span class="params">(<span class="keyword">final</span> PolledConfigurationSource source, <span class="keyword">final</span> Configuration config)</span> </span>{</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">new</span> Runnable() {</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">run</span><span class="params">()</span> </span>{</span><br><span class="line"> log.debug(<span class="string">"Polling started"</span>);</span><br><span class="line"> PollResult result = <span class="keyword">null</span>;</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//重点是这里,拉取配置结果</span></span><br><span class="line"> result = source.poll(<span class="keyword">false</span>, getNextCheckPoint(checkPoint));</span><br><span class="line"> checkPoint = result.getCheckPoint();</span><br><span class="line"> fireEvent(EventType.POLL_SUCCESS, result, <span class="keyword">null</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> log.error(<span class="string">"Error getting result from polling source"</span>, e);</span><br><span class="line"> fireEvent(EventType.POLL_FAILURE, <span class="keyword">null</span>, e);</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//更新配置</span></span><br><span class="line"> populateProperties(result, config);</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> log.error(<span class="string">"Error occured applying properties"</span>, e);</span><br><span class="line"> } </span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> }; </span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>以上代码是先初始化,从url中拉取配置,然后开启一个线程,间隔一段时间拉取,然后调用populateProperties方法更新配置。<br>source.poll代码比较简单,获取url资源,得到Properties,转换为map,封装在<code>PollResult</code>中</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> PollResult <span class="title">poll</span><span class="params">(<span class="keyword">boolean</span> initial, Object checkPoint)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> IOException </span>{ </span><br><span class="line"> <span class="keyword">if</span> (configUrls == <span class="keyword">null</span> || configUrls.length == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span> PollResult.createFull(<span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> Map<String, Object> map = <span class="keyword">new</span> HashMap<String, Object>();</span><br><span class="line"> <span class="keyword">for</span> (URL url: configUrls) {</span><br><span class="line"> InputStream fin = url.openStream();</span><br><span class="line"> Properties props = ConfigurationUtils.loadPropertiesFromInputStream(fin);</span><br><span class="line"> <span class="keyword">for</span> (Entry<Object, Object> entry: props.entrySet()) {</span><br><span class="line"> map.put((String) entry.getKey(), entry.getValue());</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> PollResult.createFull(map);</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>populateProperties更新配置的代码:<br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">populateProperties</span><span class="params">(<span class="keyword">final</span> PollResult result, <span class="keyword">final</span> Configuration config)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (result == <span class="keyword">null</span> || !result.hasChanges()) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!result.isIncremental()) {</span><br><span class="line"> <span class="comment">//不是增量结果,即全量结果,默认使用的是全量结果</span></span><br><span class="line"> Map<String, Object> props = result.getComplete();</span><br><span class="line"> <span class="keyword">if</span> (props == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//遍历每个配置项,进行更新</span></span><br><span class="line"> <span class="keyword">for</span> (Entry<String, Object> entry: props.entrySet()) {</span><br><span class="line"> propertyUpdater.addOrChangeProperty(entry.getKey(), entry.getValue(), config);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//下面这段逻辑是删除配置项</span></span><br><span class="line"> HashSet<String> existingKeys = <span class="keyword">new</span> HashSet<String>();</span><br><span class="line"> <span class="keyword">for</span> (Iterator<String> i = config.getKeys(); i.hasNext();) {</span><br><span class="line"> existingKeys.add(i.next());</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">if</span> (!ignoreDeletesFromSource) {</span><br><span class="line"> <span class="keyword">for</span> (String key: existingKeys) {</span><br><span class="line"> <span class="keyword">if</span> (!props.containsKey(key)) {</span><br><span class="line"> propertyUpdater.deleteProperty(key, config);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//增量结果,默认情况下是全量,一般不会走这里</span></span><br><span class="line"> 。。。</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>先遍历所有配置项进行更新,然后处理删除配置项。通过propertyUpdater.addOrChangeProperty更新配置型,代码:</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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">void</span> <span class="title">addOrChangeProperty</span><span class="params">(<span class="keyword">final</span> String name, <span class="keyword">final</span> Object newValue, <span class="keyword">final</span> Configuration config)</span> </span>{</span><br><span class="line"> <span class="comment">// We do not want to abort the operation due to failed validation on one property</span></span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//config中不包含配置项就增加</span></span><br><span class="line"> <span class="keyword">if</span> (!config.containsKey(name)) {</span><br><span class="line"> logger.debug(<span class="string">"adding property key [{}], value [{}]"</span>, name, newValue);</span><br><span class="line"></span><br><span class="line"> config.addProperty(name, newValue);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> <span class="comment">//包含的话就比较</span></span><br><span class="line"> Object oldValue = config.getProperty(name);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">if</span> (newValue != <span class="keyword">null</span>) {</span><br><span class="line"> Object newValueArray;</span><br><span class="line"> <span class="comment">//数组处理</span></span><br><span class="line"> <span class="keyword">if</span> (oldValue <span class="keyword">instanceof</span> CopyOnWriteArrayList && AbstractConfiguration.getDefaultListDelimiter() != <span class="string">'\0'</span>){</span><br><span class="line"> newValueArray =</span><br><span class="line"> <span class="keyword">new</span> CopyOnWriteArrayList();</span><br><span class="line"></span><br><span class="line"> Iterable<String> stringiterator = Splitter.on(AbstractConfiguration.getDefaultListDelimiter()).omitEmptyStrings().trimResults().split((String)newValue);</span><br><span class="line"> <span class="keyword">for</span>(String s :stringiterator){</span><br><span class="line"> ((CopyOnWriteArrayList) newValueArray).add(s);</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> newValueArray = newValue;</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//不相同修改配置</span></span><br><span class="line"> <span class="keyword">if</span> (!newValueArray.equals(oldValue)) {</span><br><span class="line"> logger.debug(<span class="string">"updating property key [{}], value [{}]"</span>, name, newValue);</span><br><span class="line"></span><br><span class="line"> config.setProperty(name, newValue);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> } <span class="keyword">else</span> <span class="keyword">if</span> (oldValue != <span class="keyword">null</span>) {</span><br><span class="line"> logger.debug(<span class="string">"nulling out property key [{}]"</span>, name);</span><br><span class="line"></span><br><span class="line"> config.setProperty(name, <span class="keyword">null</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (ValidationException e) {</span><br><span class="line"> logger.warn(<span class="string">"Validation failed for property "</span> + name, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>更新配置项的过程是拿出老的配置值跟新的配置值进行比较,不相同才修改,还处理了下数组配置项。这里的config是<code>DynamicURLConfiguration</code>,setProperty方法中触发事件:<br><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><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setProperty</span><span class="params">(String key, Object value)</span> <span class="keyword">throws</span> ValidationException</span></span><br><span class="line"><span class="function"></span>{</span><br><span class="line"> <span class="keyword">if</span> (value == <span class="keyword">null</span>) {</span><br><span class="line"> <span class="keyword">throw</span> <span class="keyword">new</span> NullPointerException(<span class="string">"Value for property "</span> + key + <span class="string">" is null"</span>);</span><br><span class="line"> }</span><br><span class="line"> fireEvent(EVENT_SET_PROPERTY, key, value, <span class="keyword">true</span>);</span><br><span class="line"> setPropertyImpl(key, value);</span><br><span class="line"> fireEvent(EVENT_SET_PROPERTY, key, value, <span class="keyword">false</span>);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>setPropertyImpl方法修改<code>DynamicURLConfiguration</code>内部保存的配置项,这里虽然改了<code>DynamicURLConfiguration</code>配置项值,但是真正用到的地方存在<code>DynamicProperty</code>中,触发fireEvent方法才会去修改<code>DynamicProperty</code>中的值:<br><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></pre></td><td class="code"><pre><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">protected</span> <span class="keyword">void</span> <span class="title">fireEvent</span><span class="params">(<span class="keyword">int</span> type, String propName, Object propValue, <span class="keyword">boolean</span> beforeUpdate)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (listeners == <span class="keyword">null</span> || listeners.size() == <span class="number">0</span>) {</span><br><span class="line"> <span class="keyword">return</span>;</span><br><span class="line"> }</span><br><span class="line"> ConfigurationEvent event = createEvent(type, propName, propValue, beforeUpdate);</span><br><span class="line"> <span class="comment">//通知每个监听器,这里listeners是一个CopyOnWriteArrayList类型,防止在遍历过程中更新数据产生问题</span></span><br><span class="line"> <span class="keyword">for</span> (ConfigurationListener l: listeners)</span><br><span class="line"> {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> l.configurationChanged(event);</span><br><span class="line"> } <span class="keyword">catch</span> (ValidationException e) {</span><br><span class="line"> <span class="keyword">if</span> (beforeUpdate) {</span><br><span class="line"> <span class="keyword">throw</span> e;</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.error(<span class="string">"Unexpected exception"</span>, e); </span><br><span class="line"> }</span><br><span class="line"> } <span class="keyword">catch</span> (Throwable e) {</span><br><span class="line"> logger.error(<span class="string">"Error firing configuration event"</span>, e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>DynamicURLConfiguration</code>添加到<code>ConcurrentCompositeConfiguration</code>中时,<code>ConcurrentCompositeConfiguration</code>会给每个Configuration增加一个<code>ConfigurationListener eventPropagater</code>:<br><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></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addConfigurationAtIndex</span><span class="params">(AbstractConfiguration config, String name, <span class="keyword">int</span> index)</span></span></span><br><span class="line"><span class="function"> <span class="keyword">throws</span> IndexOutOfBoundsException </span>{</span><br><span class="line"> <span class="keyword">if</span> (!configList.contains(config)) {</span><br><span class="line"> checkIndex(index);</span><br><span class="line"> configList.add(index, config);</span><br><span class="line"> <span class="keyword">if</span> (name != <span class="keyword">null</span>) {</span><br><span class="line"> namedConfigurations.put(name, config);</span><br><span class="line"> }</span><br><span class="line"> <span class="comment">//将自己的属性eventPropagater添加到config的监听列表中</span></span><br><span class="line"> config.addConfigurationListener(eventPropagater);</span><br><span class="line"> fireEvent(EVENT_CONFIGURATION_SOURCE_CHANGED, <span class="keyword">null</span>, <span class="keyword">null</span>, <span class="keyword">false</span>);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> logger.warn(config + <span class="string">" is not added as it already exits"</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p><code>DynamicURLConfiguration</code>中就有一个<code>ConfigurationListener</code>的监听器:</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></pre></td><td class="code"><pre><span class="line"><span class="comment">//ConcurrentCompositeConfiguration监听器属性初始化</span></span><br><span class="line"><span class="keyword">private</span> ConfigurationListener eventPropagater = <span class="keyword">new</span> ConfigurationListener() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">configurationChanged</span><span class="params">(ConfigurationEvent event)</span> </span>{</span><br><span class="line"> <span class="keyword">boolean</span> beforeUpdate = event.isBeforeUpdate();</span><br><span class="line"> <span class="keyword">if</span> (propagateEventToParent) {</span><br><span class="line"> <span class="keyword">int</span> type = event.getType();</span><br><span class="line"> String name = event.getPropertyName();</span><br><span class="line"> Object value = event.getPropertyValue();</span><br><span class="line"> Object finalValue;</span><br><span class="line"> <span class="keyword">switch</span>(type) {</span><br><span class="line"> <span class="keyword">case</span> HierarchicalConfiguration.EVENT_ADD_NODES:</span><br><span class="line"> <span class="keyword">case</span> EVENT_CLEAR:</span><br><span class="line"> <span class="keyword">case</span> EVENT_CONFIGURATION_SOURCE_CHANGED:</span><br><span class="line"> fireEvent(type, name, value, beforeUpdate);</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">case</span> EVENT_ADD_PROPERTY:</span><br><span class="line"> <span class="keyword">case</span> EVENT_SET_PROPERTY:</span><br><span class="line"> <span class="comment">//不管是beforeUpdate是true还是false,都会调用自己的fireEvent方法</span></span><br><span class="line"> <span class="keyword">if</span> (beforeUpdate) {</span><br><span class="line"> <span class="comment">// we want the validators to run even if the source is not</span></span><br><span class="line"> <span class="comment">// the winning configuration</span></span><br><span class="line"> fireEvent(type, name, value, beforeUpdate); </span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> AbstractConfiguration sourceConfig = (AbstractConfiguration) event.getSource();</span><br><span class="line"> AbstractConfiguration winningConf = (AbstractConfiguration) getSource(name);</span><br><span class="line"> <span class="keyword">if</span> (winningConf == <span class="keyword">null</span> || getIndexOfConfiguration(sourceConfig) <= getIndexOfConfiguration(winningConf)) {</span><br><span class="line"> fireEvent(type, name, value, beforeUpdate); </span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">case</span> EVENT_CLEAR_PROPERTY:</span><br><span class="line"> finalValue = ConcurrentCompositeConfiguration.<span class="keyword">this</span>.getProperty(name);</span><br><span class="line"> <span class="keyword">if</span> (finalValue == <span class="keyword">null</span>) {</span><br><span class="line"> fireEvent(type, name, value, beforeUpdate); </span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> fireEvent(EVENT_SET_PROPERTY, name, finalValue, beforeUpdate);</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"> <span class="keyword">default</span>:</span><br><span class="line"> <span class="keyword">break</span>;</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> } </span><br><span class="line"> } </span><br><span class="line">};</span><br></pre></td></tr></table></figure><p>可以看到,最后都调用<code>ConcurrentCompositeConfiguration</code>的fireEvent方法,fireEvent是从父类继承过来的,作用是调用所有监听器,那么<code>ConcurrentCompositeConfiguration</code>的监听器有哪些呢?我们调试过程中发现有两个:</p><ul><li>ExpandedConfigurationListenerAdapter 配置监听适配器</li><li>ConfigurationBasedDeploymentContext.configListener 部署监听器,环境配置发生改变时,修改环境配置项</li></ul><p>重要的是这个<code>ExpandedConfigurationListenerAdapter</code>,作用是将arhaius定义的监听器适配到Apache定义的监听器上,一个典型的适配器模式。<code>ExpandedConfigurationListenerAdapter</code>含有一个<code>PropertyListener expandedListener</code>属性,实现类是<code>DynamicProperty</code>的内部类<code>DynamicPropertyListener</code>。初始化的地方在<code>DynamicPropertyFactory</code>方法setDirect中:<br><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><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">setDirect</span><span class="params">(DynamicPropertySupport support)</span> </span>{</span><br><span class="line"> <span class="keyword">synchronized</span> (ConfigurationManager.class) {</span><br><span class="line"> config = support;</span><br><span class="line"> DynamicProperty.registerWithDynamicPropertySupport(support);</span><br><span class="line"> initializedWithDefaultConfig = <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>DynamicProperty.registerWithDynamicPropertySupport</code>方法:</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><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">void</span> <span class="title">registerWithDynamicPropertySupport</span><span class="params">(DynamicPropertySupport config)</span> </span>{</span><br><span class="line"> initialize(config);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="function"><span class="keyword">static</span> <span class="keyword">synchronized</span> <span class="keyword">void</span> <span class="title">initialize</span><span class="params">(DynamicPropertySupport config)</span> </span>{</span><br><span class="line"> dynamicPropertySupportImpl = config;</span><br><span class="line"> <span class="comment">//这里新建DynamicPropertyListener增加到config中</span></span><br><span class="line"> config.addConfigurationListener(<span class="keyword">new</span> DynamicPropertyListener());</span><br><span class="line"> updateAllProperties();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>这里的config实现类是<code>ConfigurationBackedDynamicPropertySupportImpl</code>,同样是一个适配器模式,将apache的<code>DynamicPropertySupport</code>适配为archaius自己的<code>DynamicPropertySupport</code>,在<code>DynamicProperty</code>里面会用到。我们给出类图:</p><p><img src="/2018/05/14/netflix-archaius/listener.png" alt=""></p><p>根据前面的分析,fireEvent触发的事件最终会适配到<code>DynamicPropertyListener</code>的setProperty方法:<br><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></pre></td><td class="code"><pre><span class="line"><span class="comment">//事件触发中调用setProperty</span></span><br><span class="line"><span class="meta">@Override</span></span><br><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">setProperty</span><span class="params">(Object source, String name, Object value, <span class="keyword">boolean</span> beforeUpdate)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (!beforeUpdate) {</span><br><span class="line"> updateProperty(name, value);</span><br><span class="line"> } <span class="keyword">else</span> {</span><br><span class="line"> validate(name, value);</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"><span class="comment">//更新配置</span></span><br><span class="line"><span class="function"><span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">boolean</span> <span class="title">updateProperty</span><span class="params">(String propName, Object value)</span> </span>{</span><br><span class="line"> <span class="comment">//从ALL_PROPS中找出动态配置,更新value</span></span><br><span class="line"> DynamicProperty prop = ALL_PROPS.get(propName);</span><br><span class="line"> <span class="keyword">if</span> (prop != <span class="keyword">null</span> && prop.updateValue(value)) {</span><br><span class="line"> <span class="comment">//通知回调</span></span><br><span class="line"> prop.notifyCallbacks();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"><span class="comment">//更新stringValue值,然后刷新缓存</span></span><br><span class="line"><span class="function"><span class="keyword">boolean</span> <span class="title">updateValue</span><span class="params">(Object newValue)</span> </span>{</span><br><span class="line"> String nv = (newValue == <span class="keyword">null</span>) ? <span class="keyword">null</span> : newValue.toString();</span><br><span class="line"> <span class="keyword">synchronized</span> (lock) {</span><br><span class="line"> <span class="keyword">if</span> ((nv == <span class="keyword">null</span> && stringValue == <span class="keyword">null</span>)</span><br><span class="line"> || (nv != <span class="keyword">null</span> && nv.equals(stringValue))) {</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">false</span>;</span><br><span class="line"> }</span><br><span class="line"> stringValue = nv;</span><br><span class="line"> cachedStringValue.flush();</span><br><span class="line"> booleanValue.flush();</span><br><span class="line"> integerValue.flush();</span><br><span class="line"> floatValue.flush();</span><br><span class="line"> classValue.flush();</span><br><span class="line"> doubleValue.flush();</span><br><span class="line"> longValue.flush();</span><br><span class="line"> changedTime = System.currentTimeMillis();</span><br><span class="line"> <span class="keyword">return</span> <span class="keyword">true</span>;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>看到这里,才是真正的将<code>DynamicProperty</code>值更新了,<code>DynamicLongProperty</code>在更新之后获取数据时,会使用新值。 </p><p>接下来我们来看回调,<code>DynamicProperty</code>的notifyCallbacks方法:<br><figure class="highlight plain"><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><span class="line">private void notifyCallbacks() {</span><br><span class="line"> //遍历所有的callbacks,执行run</span><br><span class="line"> for (Runnable r : callbacks) {</span><br><span class="line"> try {</span><br><span class="line"> r.run();</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> logger.error("Error in DynamicProperty callback", e);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里callbacks是<code>DynamicProperty</code>的属性,在给动态配置添加回调函数时,直接添加到<code>DynamicProperty.callbacks</code>属性中,来看<code>PropertyWrapper</code>类的addCallback犯法:</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><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">addCallback</span><span class="params">(Runnable callback)</span> </span>{</span><br><span class="line"> <span class="keyword">if</span> (callback != <span class="keyword">null</span>) {</span><br><span class="line"> <span class="comment">//这里的prop就是DynamicProperty</span></span><br><span class="line"> prop.addCallback(callback);</span><br><span class="line"> callbackList.add(callback);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>到这里,动态更新和回调就分析完了。 </p><p>有点复杂,我们再捋一遍:</p><ol><li><code>DynamicURLConfiguration</code>中的线程间隔一段时间从url中拉取配置</li><li>拉取配置后调用<code>AbstractPollingScheduler</code>类的populateProperties方法更新配置</li><li>populateProperties方法中调用属性<code>DynamicPropertyUpdater propertyUpdater</code>的addOrChangeProperty方法</li><li><code>DynamicPropertyUpdater</code>的方法addOrChangeProperty会进行新老配置型对比,调用<code>DynamicURLConfiguration</code>的setProperty方法修改变更项</li><li><code>DynamicURLConfiguration</code>的setProperty方法先修改本身存放的配置项,然后触发事件<code>fireEvent</code></li><li><code>DynamicURLConfiguration</code>中的监听器是<code>ConcurrentCompositeConfiguration</code>的属性<code>ConfigurationListener eventPropagater</code>,<code>ConcurrentCompositeConfiguration</code>每次增加配置源是会给每个源添加eventPropagater监听器</li><li><code>DynamicURLConfiguration</code>的fireEvent会通过eventPropagater触发<code>ConcurrentCompositeConfiguration</code>的fireEvent</li><li><code>ConcurrentCompositeConfiguration</code>的监听器包含一个<code>ExpandedConfigurationListenerAdapter</code>的适配器,包含一个<code>PropertyListener</code>的属性,将事件转到<code>PropertyListener</code>中</li><li><code>PropertyListener</code>的实现类是<code>DynamicProperty</code>的内部类<code>DynamicPropertyListener</code>,里面会调用<code>DynamicProperty</code>的静态方法<code>updateProperty</code></li><li><code>DynamicProperty</code>的<code>updateProperty</code>方法先从<code>ALL_PROPS</code>中获取配置<code>DynamicProperty</code>,更新配置项,然后通知所有的回调。</li></ol>]]></content>
<summary type="html">
<h1 id="archaius介绍"><a href="#archaius介绍" class="headerlink" title="archaius介绍"></a>archaius介绍</h1><p>archaius是Netflix公司开源项目之一,基于java的配置管理类库,主要用于多配置存储的动态获取。主要功能是对apache common configuration类库的扩展。在云平台开发中可以将其用作分布式配置管理依赖构件。同时,它有如下一些特性:</p>
<ul>
<li>动态获取属性</li>
<li>高效和线程安全的配置操作</li>
<li>配置改变时提供回调机制</li>
<li>可以通过jmx操作配置</li>
<li>复合配置</li>
</ul>
</summary>
<category term="spring" scheme="http://along101.site/categories/spring/"/>
<category term="netflix" scheme="http://along101.site/tags/netflix/"/>
<category term="archaius" scheme="http://along101.site/tags/archaius/"/>
<category term="config" scheme="http://along101.site/tags/config/"/>
</entry>
<entry>
<title>java aio socket 原理分析</title>
<link href="http://along101.site/2018/01/29/java-aio-socket/"/>
<id>http://along101.site/2018/01/29/java-aio-socket/</id>
<published>2018-01-29T17:05:57.000Z</published>
<updated>2018-07-20T03:10:04.405Z</updated>
<content type="html"><![CDATA[<h1 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h1><p>Java AIO就是Java作为对异步IO提供支持的NIO.2 ,Java NIO2 (JSR 203)定义了更多的 New I/O APIs, 提案2003提出,直到2011年才发布, 最终在JDK 7中才实现。</p><p>从编程模式上来看AIO相对于NIO的区别在于,NIO需要使用者线程不停的轮询IO对象,来确定是否有数据准备好可以读了,而AIO则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了。</p><p>当然AIO的异步特性并不是Java实现的伪异步,而是使用了系统底层API的支持,在Unix系统下,采用了epoll IO模型,而windows便是使用了IOCP模型。</p><a id="more"></a><h1 id="java-aio-socket-源代码解析"><a href="#java-aio-socket-源代码解析" class="headerlink" title="java aio socket 源代码解析"></a>java aio socket 源代码解析</h1><p>先上demo,sever端代码如下<br><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><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 接收客户端发送数据,返回</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AIOEchoServer</span> </span>{</span><br><span class="line"> <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> port;</span><br><span class="line"></span><br><span class="line"> <span class="keyword">private</span> AsynchronousServerSocketChannel server = <span class="keyword">null</span>;</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="title">AIOEchoServer</span><span class="params">(<span class="keyword">int</span> port)</span> </span>{</span><br><span class="line"> <span class="keyword">this</span>.port = port;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">listen</span><span class="params">()</span> <span class="keyword">throws</span> IOException </span>{</span><br><span class="line"> server = AsynchronousServerSocketChannel.open().bind(<span class="keyword">new</span> InetSocketAddress(port));</span><br><span class="line"> <span class="comment">//accept客户端来的连接,生成一个channel,异步回调CompletionHandler</span></span><br><span class="line"> server.accept(<span class="keyword">null</span>, <span class="keyword">new</span> CompletionHandler<AsynchronousSocketChannel, Object>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">completed</span><span class="params">(AsynchronousSocketChannel channel, Object attachment)</span> </span>{</span><br><span class="line"> <span class="comment">//这里得到一个channel,在这个channel上读取数据</span></span><br><span class="line"> ByteBuffer readBuff = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> <span class="comment">//如果没有数据可以读取,这个方法会阻塞</span></span><br><span class="line"> channel.read(readBuff, readBuff, <span class="keyword">new</span> CompletionHandler<Integer, ByteBuffer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">completed</span><span class="params">(Integer result, ByteBuffer readBuff)</span> </span>{</span><br><span class="line"> readBuff.flip();</span><br><span class="line"> <span class="keyword">if</span> (readBuff.remaining() > <span class="number">0</span>) {</span><br><span class="line"> String value = <span class="keyword">new</span> String(readBuff.array(), <span class="number">0</span>, readBuff.limit());</span><br><span class="line"> System.out.println(<span class="string">"receive from client: "</span> + value);</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> value = value + <span class="string">" from server."</span>;</span><br><span class="line"> System.out.println(<span class="string">"write to client: "</span> + value);</span><br><span class="line"> <span class="comment">//写回客户端,用同步的方式,也可以用异步方式。如果客户端数据一次没有读取完,往channel里面写会抛异常,需要粘包</span></span><br><span class="line"> channel.write(ByteBuffer.wrap((value).getBytes())).get();</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> readBuff.clear();</span><br><span class="line"> channel.read(readBuff, readBuff, <span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> } <span class="keyword">catch</span> (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } <span class="keyword">finally</span> {</span><br><span class="line"> <span class="comment">//继续接收其他的客户端连接,如果不加这一行,只能连接一个客户端</span></span><br><span class="line"> server.accept(<span class="keyword">null</span>, <span class="keyword">this</span>);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failed</span><span class="params">(Throwable exc, Object attachment)</span> </span>{</span><br><span class="line"></span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> System.out.println(<span class="string">"Server listen on "</span> + port);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">new</span> AIOEchoServer(<span class="number">8001</span>).listen();</span><br><span class="line"> <span class="keyword">while</span> (<span class="keyword">true</span>) {</span><br><span class="line"> <span class="keyword">try</span> {</span><br><span class="line"> Thread.sleep(<span class="number">100000</span>);</span><br><span class="line"> } <span class="keyword">catch</span> (InterruptedException e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>再看client端:<br><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></pre></td><td class="code"><pre><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 向服务端发送一条消息,从服务端读取回执</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">AIOClient</span> </span>{</span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> Exception </span>{</span><br><span class="line"> <span class="keyword">final</span> AsynchronousSocketChannel clientChannel = AsynchronousSocketChannel.open();</span><br><span class="line"> InetSocketAddress serverAddress = <span class="keyword">new</span> InetSocketAddress(<span class="string">"127.0.0.1"</span>, <span class="number">8001</span>);</span><br><span class="line"> clientChannel.connect(serverAddress).get(<span class="number">1</span>, TimeUnit.SECONDS);</span><br><span class="line"></span><br><span class="line"> <span class="keyword">for</span> (<span class="keyword">int</span> i = <span class="number">0</span>; i < <span class="number">10</span>; i++) {</span><br><span class="line"> ByteBuffer writeBuffer = ByteBuffer.wrap((<span class="string">"Hello "</span> + i).getBytes());</span><br><span class="line"> <span class="comment">//这里使用一个CountDownLatch,保证从服务端读取回执后再发送下一条,同时使用一个通道发送,会抛异常</span></span><br><span class="line"> CountDownLatch countDownLatch = <span class="keyword">new</span> CountDownLatch(<span class="number">1</span>);</span><br><span class="line"> clientChannel.write(writeBuffer, writeBuffer, <span class="keyword">new</span> CompletionHandler<Integer, ByteBuffer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">completed</span><span class="params">(Integer result, ByteBuffer writeBuffer)</span> </span>{</span><br><span class="line"> writeBuffer.flip();</span><br><span class="line"> System.out.println(<span class="string">"write success "</span> + <span class="keyword">new</span> String(writeBuffer.array(), <span class="number">0</span>, writeBuffer.limit()));</span><br><span class="line"> ByteBuffer readBuffer = ByteBuffer.allocate(<span class="number">1024</span>);</span><br><span class="line"> <span class="comment">//从服务端读取返回,最多等10秒</span></span><br><span class="line"> clientChannel.read(readBuffer, <span class="number">2</span>, TimeUnit.SECONDS, readBuffer, <span class="keyword">new</span> CompletionHandler<Integer, ByteBuffer>() {</span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">completed</span><span class="params">(Integer result, ByteBuffer readBuffer)</span> </span>{</span><br><span class="line"> readBuffer.flip();</span><br><span class="line"> System.out.println(<span class="string">"receive: "</span> + <span class="keyword">new</span> String(readBuffer.array(), <span class="number">0</span>, readBuffer.limit()));</span><br><span class="line"> readBuffer.clear();</span><br><span class="line"> <span class="comment">//读完了才countDown,如果服务端发送数据大于缓冲区大小,这里是处理不了的,还需要进行包的拆分和粘连</span></span><br><span class="line"> countDownLatch.countDown();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failed</span><span class="params">(Throwable exc, ByteBuffer readBuffer)</span> </span>{</span><br><span class="line"> <span class="comment">//失败了要countDown</span></span><br><span class="line"> countDownLatch.countDown();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> <span class="meta">@Override</span></span><br><span class="line"> <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">failed</span><span class="params">(Throwable exc, ByteBuffer attachment)</span> </span>{</span><br><span class="line"> <span class="comment">//失败了要countDown</span></span><br><span class="line"> countDownLatch.countDown();</span><br><span class="line"> }</span><br><span class="line"> });</span><br><span class="line"> countDownLatch.await();</span><br><span class="line"> }</span><br><span class="line"> clientChannel.close();</span><br><span class="line"> System.out.println(<span class="string">"channel close success!"</span>);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>相对于NIO,服务端代码没有Selector,SelectionKey,不需要自己去循环,但是异步方式有些地方比较难以理解。客户端代码像javascript的ajax调用。</p><h1 id="windows上实现"><a href="#windows上实现" class="headerlink" title="windows上实现"></a>windows上实现</h1><h2 id="连接建立"><a href="#连接建立" class="headerlink" title="连接建立"></a>连接建立</h2><h3 id="先看channel的创建"><a href="#先看channel的创建" class="headerlink" title="先看channel的创建"></a>先看channel的创建</h3><figure class="highlight plain"><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><span class="line">AsynchronousServerSocketChannel.open()</span><br><span class="line">//往下跟</span><br><span class="line">provider.openAsynchronousServerSocketChannel(group);</span><br><span class="line"></span><br><span class="line"> //windows上,创建一个WindowsAsynchronousServerSocketChannelImpl</span><br><span class="line"> @Override</span><br><span class="line"> public AsynchronousServerSocketChannel openAsynchronousServerSocketChannel(AsynchronousChannelGroup group)</span><br><span class="line"> throws IOException</span><br><span class="line"> {</span><br><span class="line"> return new WindowsAsynchronousServerSocketChannelImpl(toIocp(group));</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>我们来看看WindowsAsynchronousServerSocketChannelImpl,是一个异步ServerSockethannelde的实现,包含几个重要属性</p><ul><li>FileDescriptor fd 文件描述符</li><li>InetSocketAddress localAddress 本地地址</li><li>long handle</li><li>int completionKey</li><li>Iocp iocp 一个具体的异步通道组实现</li><li>PendingIoCache ioCache</li><li>long dataBuffer</li><li>AtomicBoolean accepting</li></ul><p>异步通道组的作用是为一组通道提供资源共享,比如异步运行时共享线程池。</p><h3 id="然后是bind本地地址"><a href="#然后是bind本地地址" class="headerlink" title="然后是bind本地地址"></a>然后是bind本地地址</h3><figure class="highlight plain"><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><span class="line">try {</span><br><span class="line"> begin();</span><br><span class="line"> synchronized (stateLock) {</span><br><span class="line"> if (localAddress != null)</span><br><span class="line"> throw new AlreadyBoundException();</span><br><span class="line"> NetHooks.beforeTcpBind(fd, isa.getAddress(), isa.getPort());</span><br><span class="line"> Net.bind(fd, isa.getAddress(), isa.getPort());</span><br><span class="line"> Net.listen(fd, backlog < 1 ? 50 : backlog);</span><br><span class="line"> localAddress = Net.localAddress(fd);</span><br><span class="line"> }</span><br><span class="line">} finally {</span><br><span class="line"> end();</span><br><span class="line">}</span><br></pre></td></tr></table></figure><p>最后调用的是本地方法,将fd绑定到端口上<br><figure class="highlight plain"><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><span class="line">public static void bind(FileDescriptor fd, InetAddress addr, int port)</span><br><span class="line"> throws IOException</span><br><span class="line">{</span><br><span class="line"> bind(UNSPEC, fd, addr, port);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">static void bind(ProtocolFamily family, FileDescriptor fd,</span><br><span class="line"> InetAddress addr, int port) throws IOException</span><br><span class="line">{</span><br><span class="line"> boolean preferIPv6 = isIPv6Available() &&</span><br><span class="line"> (family != StandardProtocolFamily.INET);</span><br><span class="line"> bind0(fd, preferIPv6, exclusiveBind, addr, port);</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">private static native void bind0(FileDescriptor fd, boolean preferIPv6,</span><br><span class="line"> boolean useExclBind, InetAddress addr,</span><br><span class="line"> int port)</span><br><span class="line"> throws IOException;</span><br></pre></td></tr></table></figure></p><h3 id="接下来是AsynchronousServerSocketChannel-accept"><a href="#接下来是AsynchronousServerSocketChannel-accept" class="headerlink" title="接下来是AsynchronousServerSocketChannel.accept"></a>接下来是AsynchronousServerSocketChannel.accept</h3><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">public abstract <A> void accept(A attachment,CompletionHandler<AsynchronousSocketChannel,? super A> handler);</span><br></pre></td></tr></table></figure><p>accept是一个抽象方法,包含两个参数:</p><ul><li>attachment 附加对象,跟nio中注册到Selector上的附加对象一样</li><li>handler accept成功后回调处理逻辑</li></ul><p>accept实现方法:</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line"> //AsynchronousServerSocketChannelImpl类</span><br><span class="line"> public final <A> void accept(A attachment, CompletionHandler<AsynchronousSocketChannel,? super A> handler) {</span><br><span class="line"> if (handler == null)</span><br><span class="line"> throw new NullPointerException("'handler' is null");</span><br><span class="line"> implAccept(attachment, (CompletionHandler<AsynchronousSocketChannel,Object>)handler);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //WindowsAsynchronousServerSocketChannelImpl类</span><br><span class="line"> @Override</span><br><span class="line"> Future<AsynchronousSocketChannel> implAccept(Object attachment, final CompletionHandler<AsynchronousSocketChannel,Object> handler) {</span><br><span class="line">。。。</span><br><span class="line"> //创建一个被accecped的socket</span><br><span class="line"> WindowsAsynchronousSocketChannelImpl ch = null;</span><br><span class="line"> IOException ioe = null;</span><br><span class="line"> try {</span><br><span class="line"> begin();</span><br><span class="line"> ch = new WindowsAsynchronousSocketChannelImpl(iocp, false);</span><br><span class="line"> } catch (IOException x) {</span><br><span class="line"> ioe = x;</span><br><span class="line"> } finally {</span><br><span class="line"> end();</span><br><span class="line"> }</span><br><span class="line"> //出现异常,直接调用handle</span><br><span class="line"> if (ioe != null) {</span><br><span class="line"> if (handler == null)</span><br><span class="line"> return CompletedFuture.withFailure(ioe);</span><br><span class="line"> Invoker.invokeIndirectly(this, handler, attachment, null, ioe);</span><br><span class="line"> return null;</span><br><span class="line"> }</span><br><span class="line"> //通过getSecurityManager检查</span><br><span class="line"> AccessControlContext acc = (System.getSecurityManager() == null) ? null : AccessController.getContext();</span><br><span class="line"> //PendingFuture实现Future接口,存放this,handler和attachment</span><br><span class="line"> PendingFuture<AsynchronousSocketChannel,Object> result = new PendingFuture<AsynchronousSocketChannel,Object>(this, handler, attachment);</span><br><span class="line"> //AcceptTask实现Runnable接口,</span><br><span class="line"> AcceptTask task = new AcceptTask(ch, acc, result);</span><br><span class="line"> result.setContext(task);</span><br><span class="line"> //并发控制</span><br><span class="line"> if (!accepting.compareAndSet(false, true))</span><br><span class="line"> throw new AcceptPendingException();</span><br><span class="line"></span><br><span class="line"> // 检查操作系统是否支持线程无关的I/O,什么意思?</span><br><span class="line"> if (Iocp.supportsThreadAgnosticIo()) {</span><br><span class="line"> //我的windows上跑这里</span><br><span class="line"> task.run();</span><br><span class="line"> } else {</span><br><span class="line"> Invoker.invokeOnThreadInThreadPool(this, task);</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>AcceptTask的run方法如下<br><figure class="highlight plain"><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><span class="line"> @Override</span><br><span class="line"> public void run() {</span><br><span class="line"> long overlapped = 0L;</span><br><span class="line">。。。</span><br><span class="line"> synchronized (result) {</span><br><span class="line"> overlapped = ioCache.add(result);</span><br><span class="line"> //重点是这行,调用native方法,windows上直接返回</span><br><span class="line"> int n = accept0(handle, channel.handle(), overlapped, dataBuffer);</span><br><span class="line"> if (n == IOStatus.UNAVAILABLE) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> finishAccept();</span><br><span class="line"></span><br><span class="line"> enableAccept();</span><br><span class="line"> //将接收到的channel设置到result中,Future的get方法阻塞就会唤醒</span><br><span class="line"> result.setResult(channel);</span><br><span class="line"> }</span><br><span class="line">。。。</span><br><span class="line"> if (result.isCancelled()) {</span><br><span class="line"> closeChildChannel();</span><br><span class="line"> }</span><br><span class="line"> //最后调用handler,这里Invoker是一个帮助类,实际上是根据是否有异常调用CompletionHandler的completed/failed方法</span><br><span class="line"> Invoker.invokeIndirectly(result);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>调试到这里,accept方法实际上已经返回了,那么,当客户端有连接过来的时候,是如何调用回调函数的呢?我们来看看accept0的native方法,打开WindowsAsynchronousServerSocketChannelImpl.c<br><figure class="highlight plain"><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><span class="line">JNIEXPORT jint JNICALL</span><br><span class="line">Java_sun_nio_ch_WindowsAsynchronousServerSocketChannelImpl_accept0(JNIEnv* env, jclass this,</span><br><span class="line"> jlong listenSocket, jlong acceptSocket, jlong ov, jlong buf)</span><br><span class="line">{</span><br><span class="line"> BOOL res;</span><br><span class="line"> SOCKET s1 = (SOCKET)jlong_to_ptr(listenSocket);</span><br><span class="line"> SOCKET s2 = (SOCKET)jlong_to_ptr(acceptSocket);</span><br><span class="line"> PVOID outputBuffer = (PVOID)jlong_to_ptr(buf);</span><br><span class="line"></span><br><span class="line"> DWORD nread = 0;</span><br><span class="line"> OVERLAPPED* lpOverlapped = (OVERLAPPED*)jlong_to_ptr(ov);</span><br><span class="line"> ZeroMemory((PVOID)lpOverlapped, sizeof(OVERLAPPED));</span><br><span class="line"> </span><br><span class="line"> res = (*AcceptEx_func)(s1, //一参本地监听Socket </span><br><span class="line"> s2, //二参为即将到来的客人准备好的Socket </span><br><span class="line"> outputBuffer,//三参接收缓冲区: 一存客人发来的第一份数据、二存Server本地地址、三存Client远端地址 地址包括IP和端口, </span><br><span class="line"> 0, //四参定三参数据区长度,0表只连不接收、连接到来->请求完成,否则连接到来+任意长数据到来->请求完成 </span><br><span class="line"> sizeof(SOCKETADDRESS)+16,//五参定三参本地地址区长度,至少sizeof(sockaddr_in) + 16 </span><br><span class="line"> sizeof(SOCKETADDRESS)+16, //六参定三参远端地址区长度,至少sizeof(sockaddr_in) + 16 </span><br><span class="line"> &nread,//标识接收到的字节数</span><br><span class="line"> lpOverlapped);//一个OVERLAPPED结构,传递数据。</span><br><span class="line"> if (res == 0) {</span><br><span class="line"> int error = WSAGetLastError();</span><br><span class="line"> if (error == ERROR_IO_PENDING) {</span><br><span class="line"> return IOS_UNAVAILABLE;</span><br><span class="line"> }</span><br><span class="line"> JNU_ThrowIOExceptionWithLastError(env, "AcceptEx failed");</span><br><span class="line"> return IOS_THROWN;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> return 0;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>看不太明白。。。在看看前面的代码,C语言很多都不记得了,复习了下,搞了个大概<br><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">//定义一个函数指针,后面会指向windows上的函数AcceptEx</span><br><span class="line">typedef BOOL (*AcceptEx_t)</span><br><span class="line">(</span><br><span class="line"> SOCKET sListenSocket,</span><br><span class="line"> SOCKET sAcceptSocket,</span><br><span class="line"> PVOID lpOutputBuffer,</span><br><span class="line"> DWORD dwReceiveDataLength,</span><br><span class="line"> DWORD dwLocalAddressLength,</span><br><span class="line"> DWORD dwRemoteAddressLength,</span><br><span class="line"> LPDWORD lpdwBytesReceived,</span><br><span class="line"> LPOVERLAPPED lpOverlapped</span><br><span class="line">);</span><br><span class="line"></span><br><span class="line">//换个名字</span><br><span class="line">static AcceptEx_t AcceptEx_func;</span><br><span class="line"></span><br><span class="line">//native的initIDs方法</span><br><span class="line">JNIEXPORT void JNICALL</span><br><span class="line">Java_sun_nio_ch_WindowsAsynchronousServerSocketChannelImpl_initIDs(JNIEnv* env, jclass this) {</span><br><span class="line"> GUID GuidAcceptEx = WSAID_ACCEPTEX;</span><br><span class="line"> SOCKET s;</span><br><span class="line"> int rv;</span><br><span class="line"> DWORD dwBytes;</span><br><span class="line"></span><br><span class="line"> s = socket(AF_INET, SOCK_STREAM, 0);</span><br><span class="line"> if (s == INVALID_SOCKET) {</span><br><span class="line"> JNU_ThrowIOExceptionWithLastError(env, "socket failed");</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> //windows的WSAIoctl函数</span><br><span class="line"> rv = WSAIoctl(s,</span><br><span class="line"> SIO_GET_EXTENSION_FUNCTION_POINTER,</span><br><span class="line"> (LPVOID)&GuidAcceptEx,</span><br><span class="line"> sizeof(GuidAcceptEx),</span><br><span class="line"> &AcceptEx_func,//给函数指针赋值</span><br><span class="line"> sizeof(AcceptEx_func),</span><br><span class="line"> &dwBytes,</span><br><span class="line"> NULL,</span><br><span class="line"> NULL);</span><br><span class="line"> if (rv != 0)</span><br><span class="line"> JNU_ThrowIOExceptionWithLastError(env, "WSAIoctl failed");</span><br><span class="line"> closesocket(s);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里通过WSAIoctl获取AcceptEx函数指针,供其他地方调用。 </p><p>AcceptEx函数的解释:Windows套接字AcceptEx函数接受一个新的连接,返回本地和远程地址,并接收由客户端应用程序发送的第一块数据。关键点就在这个AcceptEx函数,调用这个函数,立即返回了,实际上没有创建连接,创建一个等待连接的数据结构体放在port指定的队列中,如果来了新的连接(客户端连接,触发事件),会将连接信息填写到结构体中,修改状态。这个连接准备好了,肯定有地方去消费这个连接,在哪里呢? </p><p>我们回到AsynchronousServerSocketChannel.open()这个位置,这里打开了一个ServerSocket,内部使用了一个Iocp,前面我们没有解释这个Iocp是什么意思,查阅资料发现,这是windows网路编程提出的一个模型,(I/O Completion Port),常称I/O完成端口。要深入这个模型,又会比较复杂,我们这里先简单的理解下,AcceptEx被调用时,会在serverSocket上注册一个回调,如果有客户端连上来了,操作系统会来处理。如何处理的呢? AsynchronousServerSocketChannel.open()的时候,会创建一个iocp java对象,内部调用native方法createIoCompletionPort创建windos的 iocp,然后将与serverSocketChannel关联。</p><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">Iocp(AsynchronousChannelProvider provider, ThreadPool pool)</span><br><span class="line"> throws IOException</span><br><span class="line">{</span><br><span class="line"> super(provider, pool);</span><br><span class="line"> //创建Iocp</span><br><span class="line"> this.port =</span><br><span class="line"> createIoCompletionPort(INVALID_HANDLE_VALUE, 0, 0, fixedThreadCount());</span><br><span class="line"> this.nextCompletionKey = 1;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">/**</span><br><span class="line"> * 与serverSocketChannel关联</span><br><span class="line"> */</span><br><span class="line">int associate(OverlappedChannel ch, long handle) throws IOException {</span><br><span class="line"> keyToChannelLock.writeLock().lock();</span><br><span class="line"></span><br><span class="line"> // generate a completion key (if not shutdown)</span><br><span class="line"> int key;</span><br><span class="line"> try {</span><br><span class="line"> if (isShutdown())</span><br><span class="line"> throw new ShutdownChannelGroupException();</span><br><span class="line"></span><br><span class="line"> // generate unique key</span><br><span class="line"> do {</span><br><span class="line"> key = nextCompletionKey++;</span><br><span class="line"> } while ((key == 0) || keyToChannel.containsKey(key));</span><br><span class="line"></span><br><span class="line"> // associate with I/O completion port</span><br><span class="line"> if (handle != 0L) {</span><br><span class="line"> //这里是关联,这里有个key,后面通知的时候,会用到这个key</span><br><span class="line"> createIoCompletionPort(handle, port, key, 0);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // 将key与ch放在hashmao缓存,后面的事件来了,会返回这个key,通过这个key找到channel</span><br><span class="line"> keyToChannel.put(key, ch);</span><br><span class="line"> } finally {</span><br><span class="line"> keyToChannelLock.writeLock().unlock();</span><br><span class="line"> }</span><br><span class="line"> return key;</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">//Iocp.java native方法</span><br><span class="line">private static native void initIDs();</span><br><span class="line"></span><br><span class="line">private static native long createIoCompletionPort(long handle,</span><br><span class="line"> long existingPort, int completionKey, int concurrency) throws IOException;</span><br><span class="line"></span><br><span class="line">private static native void close0(long handle);</span><br><span class="line"></span><br><span class="line">private static native void getQueuedCompletionStatus(long completionPort,</span><br><span class="line"> CompletionStatus status) throws IOException;</span><br><span class="line"></span><br><span class="line">private static native void postQueuedCompletionStatus(long completionPort,</span><br><span class="line"> int completionKey) throws IOException;</span><br><span class="line"></span><br><span class="line">private static native String getErrorMessage(int error);</span><br></pre></td></tr></table></figure><p>然后启动一个线程,阻塞在getQueuedCompletionStatus方法处。<br><figure class="highlight plain"><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><span class="line"></span><br><span class="line"> Iocp start() {</span><br><span class="line"> startThreads(new EventHandlerTask());</span><br><span class="line"> return this;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> private class EventHandlerTask implements Runnable {</span><br><span class="line"> public void run() {</span><br><span class="line">。。。</span><br><span class="line"> try {</span><br><span class="line"> for (;;) {</span><br><span class="line"> </span><br><span class="line"> try {</span><br><span class="line"> //这里会阻塞,有iopc完成时间会唤醒</span><br><span class="line"> getQueuedCompletionStatus(port, ioResult);</span><br><span class="line"> } catch (IOException x) {</span><br><span class="line"> // should not happen</span><br><span class="line"> x.printStackTrace();</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line">。。。</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>我们用客户端发起一个连接,阻塞的这行就会被唤醒,跳到下一行<br><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line"> // handle wakeup to execute task or shutdown</span><br><span class="line"> if (ioResult.completionKey() == 0 &&</span><br><span class="line"> ioResult.overlapped() == 0L)</span><br><span class="line"> {</span><br><span class="line"> Runnable task = pollTask();</span><br><span class="line"> if (task == null) {</span><br><span class="line"> // shutdown request</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // run task</span><br><span class="line"> // (if error/exception then replace thread)</span><br><span class="line"> replaceMe = true;</span><br><span class="line"> task.run();</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // map key to channel</span><br><span class="line"> OverlappedChannel ch = null;</span><br><span class="line"> keyToChannelLock.readLock().lock();</span><br><span class="line"> try {</span><br><span class="line"> //这里根据completionKey找到channel,这个key就是前面我们关联iocp时传入的,现在有时间了,iocp给返回了。</span><br><span class="line"> ch = keyToChannel.get(ioResult.completionKey());</span><br><span class="line"> if (ch == null) {</span><br><span class="line"> checkIfStale(ioResult.overlapped());</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> } finally {</span><br><span class="line"> keyToChannelLock.readLock().unlock();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> //根据overlapped找到关联的对象。这个overlapped是我们在accept0里面放进去的。</span><br><span class="line"> PendingFuture<?,?> result = ch.getByOverlapped(ioResult.overlapped());</span><br><span class="line"> if (result == null) {</span><br><span class="line"> // we get here if the OVERLAPPED structure is associated</span><br><span class="line"> // with an I/O operation on a channel that was closed</span><br><span class="line"> // but the I/O operation event wasn't read in a timely</span><br><span class="line"> // manner. Alternatively, it may be related to a</span><br><span class="line"> // tryLock operation as the OVERLAPPED structures for</span><br><span class="line"> // these operations are not in the I/O cache.</span><br><span class="line"> checkIfStale(ioResult.overlapped());</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // synchronize on result in case I/O completed immediately</span><br><span class="line"> // and was handled by initiator</span><br><span class="line"> synchronized (result) {</span><br><span class="line"> if (result.isDone()) {</span><br><span class="line"> continue;</span><br><span class="line"> }</span><br><span class="line"> // not handled by initiator</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // invoke I/O result handler</span><br><span class="line"> int error = ioResult.error();</span><br><span class="line"> //获取ResultHandler,实际上获取的是AcceptTask</span><br><span class="line"> ResultHandler, rh = (ResultHandler)result.getContext();</span><br><span class="line"> replaceMe = true; // (if error/exception then replace thread)</span><br><span class="line"> if (error == 0) {</span><br><span class="line"> //调用AcceptTask的complete方法,里面调用回调函数</span><br><span class="line"> rh.completed(ioResult.bytesTransferred(), canInvokeDirect);</span><br><span class="line"> } else {</span><br><span class="line"> rh.failed(error, translateErrorToIOException(error));</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>根据以上代码中的注释,我们基本上搞清楚了,如何accept一个请求后如何被唤醒的。总结下,主要是两个key,一个是将serverSocketChannel注册到Iocp中的CompletionKey,另一个是在AccetpEx函数中注册的overlapped。在java内存中用hashmap缓存key与对象关系。然后我们再看看回调函数的执行:<br><figure class="highlight plain"><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><span class="line"> @Override</span><br><span class="line"> public void completed(int bytesTransferred, boolean canInvokeDirect) {</span><br><span class="line">。。。</span><br><span class="line"> //result是一个Future对象,设置result,get的地方会唤醒返回。</span><br><span class="line"> result.setResult(channel);</span><br><span class="line">。。。</span><br><span class="line"> // 调用回调的handler,Invoker是一个工具类,将调用进行了封装</span><br><span class="line"> Invoker.invokeIndirectly(result);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>Invoker内部代码逻辑比较简单,不再这里描述了。 </p><p>到这里,整个accept的过程就分析完了。</p><h2 id="异步读取数据"><a href="#异步读取数据" class="headerlink" title="异步读取数据"></a>异步读取数据</h2><p>下面我们继续分析,是如何异步读取数据的。</p><h3 id="回到demo中的accept的回调函数completed"><a href="#回到demo中的accept的回调函数completed" class="headerlink" title="回到demo中的accept的回调函数completed"></a>回到demo中的accept的回调函数completed</h3><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line"> public void completed(AsynchronousSocketChannel channel, Object attachment) {</span><br><span class="line"> //这里得到一个channel,在这个channel上读取数据</span><br><span class="line"> ByteBuffer readBuff = ByteBuffer.allocate(1024);</span><br><span class="line"> try {</span><br><span class="line"> //如果没有数据可以读取</span><br><span class="line"> channel.read(readBuff, readBuff, new CompletionHandler<Integer, ByteBuffer>() {</span><br><span class="line">。。。。。。。</span><br><span class="line"> });</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> } finally {</span><br><span class="line"> //继续接收其他的客户端连接,如果不加这一行,只能连接一个客户端</span><br><span class="line"> server.accept(null, this);</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><p>我们跟到channel.read内部:<br><figure class="highlight plain"><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><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br></pre></td><td class="code"><pre><span class="line"> //AsynchronousSocketChannel</span><br><span class="line"> @Override</span><br><span class="line"> public final <A> void read(ByteBuffer dst,</span><br><span class="line"> A attachment,</span><br><span class="line"> CompletionHandler<Integer,? super A> handler)</span><br><span class="line"> {</span><br><span class="line"> read(dst, 0L, TimeUnit.MILLISECONDS, attachment, handler);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> //AsynchronousSocketChannelImpl </span><br><span class="line"> @Override</span><br><span class="line"> public final <A> void read(ByteBuffer dst,</span><br><span class="line"> long timeout,</span><br><span class="line"> TimeUnit unit,</span><br><span class="line"> A attachment,</span><br><span class="line"> CompletionHandler<Integer,? super A> handler)</span><br><span class="line"> {</span><br><span class="line"> if (handler == null)</span><br><span class="line"> throw new NullPointerException("'handler' is null");</span><br><span class="line"> if (dst.isReadOnly())</span><br><span class="line"> throw new IllegalArgumentException("Read-only buffer");</span><br><span class="line"> read(false, dst, null, timeout, unit, attachment, handler);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> </span><br><span class="line"> private <V extends Number,A> Future<V> read(boolean isScatteringRead,</span><br><span class="line"> ByteBuffer dst,</span><br><span class="line"> ByteBuffer[] dsts,</span><br><span class="line"> long timeout,</span><br><span class="line"> TimeUnit unit,</span><br><span class="line"> A att,</span><br><span class="line"> CompletionHandler<V,? super A> handler)</span><br><span class="line"> {</span><br><span class="line">。。。</span><br><span class="line"> return implRead(isScatteringRead, dst, dsts, timeout, unit, att, handler);</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> //WindowsAsynchronousSocketChannelImpl </span><br><span class="line"> @Override</span><br><span class="line"> <V extends Number,A> Future<V> implRead(boolean isScatteringRead,</span><br><span class="line"> ByteBuffer dst,</span><br><span class="line"> ByteBuffer[] dsts,</span><br><span class="line"> long timeout,</span><br><span class="line"> TimeUnit unit,</span><br><span class="line"> A attachment,</span><br><span class="line"> CompletionHandler<V,? super A> handler)</span><br><span class="line"> {</span><br><span class="line"> // setup task</span><br><span class="line"> PendingFuture<V,A> result =</span><br><span class="line"> new PendingFuture<V,A>(this, handler, attachment);</span><br><span class="line"> ByteBuffer[] bufs;</span><br><span class="line"> if (isScatteringRead) {</span><br><span class="line"> bufs = dsts;</span><br><span class="line"> } else {</span><br><span class="line"> bufs = new ByteBuffer[1];</span><br><span class="line"> bufs[0] = dst;</span><br><span class="line"> }</span><br><span class="line"> //新建一个ReadTask</span><br><span class="line"> final ReadTask<V,A> readTask =</span><br><span class="line"> new ReadTask<V,A>(bufs, isScatteringRead, result);</span><br><span class="line"> result.setContext(readTask);</span><br><span class="line"></span><br><span class="line"> // schedule timeout</span><br><span class="line"> if (timeout > 0L) {</span><br><span class="line"> Future<?> timeoutTask = iocp.schedule(new Runnable() {</span><br><span class="line"> public void run() {</span><br><span class="line"> readTask.timeout();</span><br><span class="line"> }</span><br><span class="line"> }, timeout, unit);</span><br><span class="line"> result.setTimeoutTask(timeoutTask);</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // initiate I/O</span><br><span class="line"> if (Iocp.supportsThreadAgnosticIo()) {</span><br><span class="line"> //这里执行这个readTask</span><br><span class="line"> readTask.run();</span><br><span class="line"> } else {</span><br><span class="line"> Invoker.invokeOnThreadInThreadPool(this, readTask);</span><br><span class="line"> }</span><br><span class="line"> return result;</span><br><span class="line"> }</span><br><span class="line"> </span><br><span class="line"> //ReadTask执行 </span><br><span class="line"> @Override</span><br><span class="line"> @SuppressWarnings("unchecked")</span><br><span class="line"> public void run() {</span><br><span class="line"> long overlapped = 0L;</span><br><span class="line"> boolean prepared = false;</span><br><span class="line"> boolean pending = false;</span><br><span class="line"></span><br><span class="line"> try {</span><br><span class="line"> begin();</span><br><span class="line"></span><br><span class="line"> // substitute non-direct buffers</span><br><span class="line"> prepareBuffers();</span><br><span class="line"> prepared = true;</span><br><span class="line"></span><br><span class="line"> // 这里又是一个overlapped</span><br><span class="line"> overlapped = ioCache.add(result);</span><br><span class="line"></span><br><span class="line"> // 调用native方法执行read,跟前面accept类似,这个方法不阻塞</span><br><span class="line"> int n = read0(handle, numBufs, readBufferArray, overlapped);</span><br><span class="line"> if (n == IOStatus.UNAVAILABLE) {</span><br><span class="line"> // I/O is pending</span><br><span class="line"> pending = true;</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> if (n == IOStatus.EOF) {</span><br><span class="line"> // input shutdown</span><br><span class="line"> enableReading();</span><br><span class="line"> if (scatteringRead) {</span><br><span class="line"> result.setResult((V)Long.valueOf(-1L));</span><br><span class="line"> } else {</span><br><span class="line"> result.setResult((V)Integer.valueOf(-1));</span><br><span class="line"> }</span><br><span class="line"> } else {</span><br><span class="line"> throw new InternalError("Read completed immediately");</span><br><span class="line"> }</span><br><span class="line"> } catch (Throwable x) {</span><br><span class="line"> // failed to initiate read</span><br><span class="line"> // reset read flag before releasing waiters</span><br><span class="line"> enableReading();</span><br><span class="line"> if (x instanceof ClosedChannelException)</span><br><span class="line"> x = new AsynchronousCloseException();</span><br><span class="line"> if (!(x instanceof IOException))</span><br><span class="line"> x = new IOException(x);</span><br><span class="line"> result.setFailure(x);</span><br><span class="line"> } finally {</span><br><span class="line"> // release resources if I/O not pending</span><br><span class="line"> if (!pending) {</span><br><span class="line"> if (overlapped != 0L)</span><br><span class="line"> ioCache.remove(overlapped);</span><br><span class="line"> if (prepared)</span><br><span class="line"> releaseBuffers();</span><br><span class="line"> }</span><br><span class="line"> end();</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> // invoke completion handler</span><br><span class="line"> Invoker.invoke(result);</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>跟accept的过程很类似,同样是通过iocp模型实现异步的,我们看看native方法read0的实现:<br><figure class="highlight plain"><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><span class="line">JNIEXPORT jint JNICALL</span><br><span class="line">Java_sun_nio_ch_WindowsAsynchronousSocketChannelImpl_read0(JNIEnv* env, jclass this,</span><br><span class="line"> jlong socket, jint count, jlong address, jlong ov)</span><br><span class="line">{</span><br><span class="line"> SOCKET s = (SOCKET) jlong_to_ptr(socket);</span><br><span class="line"> WSABUF* lpWsaBuf = (WSABUF*) jlong_to_ptr(address);</span><br><span class="line"> OVERLAPPED* lpOverlapped = (OVERLAPPED*) jlong_to_ptr(ov);</span><br><span class="line"> BOOL res;</span><br><span class="line"> DWORD flags = 0;</span><br><span class="line"></span><br><span class="line"> ZeroMemory((PVOID)lpOverlapped, sizeof(OVERLAPPED));</span><br><span class="line"> res = WSARecv(s,</span><br><span class="line"> lpWsaBuf,</span><br><span class="line"> (DWORD)count,</span><br><span class="line"> NULL,</span><br><span class="line"> &flags,</span><br><span class="line"> lpOverlapped,</span><br><span class="line"> NULL);</span><br><span class="line"></span><br><span class="line"> if (res == SOCKET_ERROR) {</span><br><span class="line"> int error = WSAGetLastError();</span><br><span class="line"> if (error == WSA_IO_PENDING) {</span><br><span class="line"> return IOS_UNAVAILABLE;</span><br><span class="line"> }</span><br><span class="line"> if (error == WSAESHUTDOWN) {</span><br><span class="line"> return IOS_EOF; // input shutdown</span><br><span class="line"> }</span><br><span class="line"> JNU_ThrowIOExceptionWithLastError(env, "WSARecv failed");</span><br><span class="line"> return IOS_THROWN;</span><br><span class="line"> }</span><br><span class="line"> return IOS_UNAVAILABLE;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里是调用windows的WSARecv方法负责注册接收数据的实现方式。有数据过来,iocp的getQueuedCompletionStatus会被唤醒,得到ioResult对象,再根据CompletionKey和overlapped得到channel和PendingFuture,然后调用这个PendingFuture中的回调函数。</p><h2 id="wirte-异步"><a href="#wirte-异步" class="headerlink" title="wirte 异步"></a>wirte 异步</h2><p>wirte异步就跟accept/read方式一样了,不再重述。</p><h2 id="附加"><a href="#附加" class="headerlink" title="附加"></a>附加</h2><p>根据我们以上分析,这里最核心的地方是iocp,其内部是有一个线程池,负责管理多个Channel,默认使用 ThreadPool.getDefault()得到这个线程池,也可以自定义这个线程池:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">AsynchronousChannelGroup threadGroup = AsynchronousChannelGroup.withThreadPool((ExecutorService) getExecutor());</span><br><span class="line">serverSock = AsynchronousServerSocketChannel.open(threadGroup);</span><br></pre></td></tr></table></figure></p><h1 id="linux实现"><a href="#linux实现" class="headerlink" title="linux实现"></a>linux实现</h1><p>打开linux源代码,找到DefaultAsynchronousChannelProvider.create,继续找到LinuxAsynchronousChannelProvider<br><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">@Override</span><br><span class="line">public AsynchronousServerSocketChannel openAsynchronousServerSocketChannel(AsynchronousChannelGroup group)</span><br><span class="line"> throws IOException</span><br><span class="line">{</span><br><span class="line"> return new UnixAsynchronousServerSocketChannelImpl(toPort(group));</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">private Port toPort(AsynchronousChannelGroup group) throws IOException {</span><br><span class="line"> if (group == null) {</span><br><span class="line"> return defaultEventPort();</span><br><span class="line"> } else {</span><br><span class="line"> if (!(group instanceof EPollPort))</span><br><span class="line"> throw new IllegalChannelGroupException();</span><br><span class="line"> return (Port)group;</span><br><span class="line"> }</span><br><span class="line">}</span><br><span class="line"></span><br><span class="line">private EPollPort defaultEventPort() throws IOException {</span><br><span class="line"> if (defaultPort == null) {</span><br><span class="line"> synchronized (LinuxAsynchronousChannelProvider.class) {</span><br><span class="line"> if (defaultPort == null) {</span><br><span class="line"> defaultPort = new EPollPort(this, ThreadPool.getDefault()).start();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return defaultPort;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>可以看到这里使用的是一个EPollPort,跟windows上的Iocp类似:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">final class EPollPort extends Port{。。。}</span><br><span class="line">abstract class Port extends AsynchronousChannelGroupImpl{。。。}</span><br></pre></td></tr></table></figure></p><p>EPollPort实际上也是一个AsynchronousChannelGroup。我们先看看UnixAsynchronousServerSocketChannelImpl<br><figure class="highlight plain"><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><span class="line"> @Override</span><br><span class="line"> Future<AsynchronousSocketChannel> implAccept(Object att,</span><br><span class="line"> CompletionHandler<AsynchronousSocketChannel,Object> handler)</span><br><span class="line"> {</span><br><span class="line">。。。</span><br><span class="line"> try {</span><br><span class="line"> begin();</span><br><span class="line"> //这里直接调用accept方法</span><br><span class="line"> int n = accept(this.fd, newfd, isaa);</span><br></pre></td></tr></table></figure></p><p>调用的是native方法:<br><figure class="highlight plain"><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><span class="line">JNIEXPORT jint JNICALL</span><br><span class="line">Java_sun_nio_ch_UnixAsynchronousServerSocketChannelImpl_accept0(JNIEnv* env,</span><br><span class="line"> jobject this, jobject ssfdo, jobject newfdo, jobjectArray isaa)</span><br><span class="line">{</span><br><span class="line"> return Java_sun_nio_ch_ServerSocketChannelImpl_accept0(env, this,</span><br><span class="line"> ssfdo, newfdo, isaa);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>找到Java_sun_nio_ch_ServerSocketChannelImpl_accept0在ServerSocketChannelImpl.c中:<br><figure class="highlight plain"><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></pre></td><td class="code"><pre><span class="line">JNIEXPORT jint JNICALL</span><br><span class="line">Java_sun_nio_ch_ServerSocketChannelImpl_accept0(JNIEnv *env, jobject this,</span><br><span class="line"> jobject ssfdo, jobject newfdo,</span><br><span class="line"> jobjectArray isaa)</span><br><span class="line">{</span><br><span class="line"> jint ssfd = (*env)->GetIntField(env, ssfdo, fd_fdID);</span><br><span class="line"> jint newfd;</span><br><span class="line"> struct sockaddr *sa;</span><br><span class="line"> int alloc_len;</span><br><span class="line"> jobject remote_ia = 0;</span><br><span class="line"> jobject isa;</span><br><span class="line"> jint remote_port;</span><br><span class="line"></span><br><span class="line"> NET_AllocSockaddr(&sa, &alloc_len);</span><br><span class="line"> if (sa == NULL) {</span><br><span class="line"> JNU_ThrowOutOfMemoryError(env, NULL);</span><br><span class="line"> return IOS_THROWN;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> /*</span><br><span class="line"> * accept connection but ignore ECONNABORTED indicating that</span><br><span class="line"> * a connection was eagerly accepted but was reset before</span><br><span class="line"> * accept() was called.</span><br><span class="line"> */</span><br><span class="line"> for (;;) {</span><br><span class="line"> socklen_t sa_len = alloc_len;</span><br><span class="line"> newfd = accept(ssfd, sa, &sa_len);</span><br><span class="line"> if (newfd >= 0) {</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> if (errno != ECONNABORTED) {</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> /* ECONNABORTED => restart accept */</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> if (newfd < 0) {</span><br><span class="line"> free((void *)sa);</span><br><span class="line"> if (errno == EAGAIN)</span><br><span class="line"> return IOS_UNAVAILABLE;</span><br><span class="line"> if (errno == EINTR)</span><br><span class="line"> return IOS_INTERRUPTED;</span><br><span class="line"> JNU_ThrowIOExceptionWithLastError(env, "Accept failed");</span><br><span class="line"> return IOS_THROWN;</span><br><span class="line"> }</span><br><span class="line"></span><br><span class="line"> (*env)->SetIntField(env, newfdo, fd_fdID, newfd);</span><br><span class="line"> remote_ia = NET_SockaddrToInetAddress(env, sa, (int *)&remote_port);</span><br><span class="line"> free((void *)sa);</span><br><span class="line"> CHECK_NULL_RETURN(remote_ia, IOS_THROWN);</span><br><span class="line"> isa = (*env)->NewObject(env, isa_class, isa_ctorID, remote_ia, remote_port);</span><br><span class="line"> CHECK_NULL_RETURN(isa, IOS_THROWN);</span><br><span class="line"> (*env)->SetObjectArrayElement(env, isaa, 0, isa);</span><br><span class="line"> return 1;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里有个循环,一直accept,accept到了才跳出去。linux环境没有搭建好,不好调试,先写到这里,下次有机会在linux中调试。</p><h2 id="Linux-kernel-AIO这个奇葩"><a href="#Linux-kernel-AIO这个奇葩" class="headerlink" title="Linux kernel AIO这个奇葩"></a>Linux kernel AIO这个奇葩</h2><p>有位技术牛人研究linux aio,写了一遍blog:<br><a href="http://www.wzxue.com/linux-kernel-aio%e8%bf%99%e4%b8%aa%e5%a5%87%e8%91%a9/" target="_blank" rel="noopener">Linux kernel AIO这个奇葩</a></p><p>这个blog打不开了,可以看csdn上的转发:<br><a href="https://blog.csdn.net/abcd1f2/article/details/47440087" target="_blank" rel="noopener">https://blog.csdn.net/abcd1f2/article/details/47440087</a></p><p>Linux AIO 早就被提上议程,目前比较知名的有 Glibc 的 AIO 与 Kernel Native AIO </p><ul><li>Glibc AIO: <a href="http://www.ibm.com/developerworks/linux/library/l-async/" target="_blank" rel="noopener">http://www.ibm.com/developerworks/linux/library/l-async/</a> </li><li>Kernel Native AIO: <a href="http://lse.sourceforge.net/io/aio.html" target="_blank" rel="noopener">http://lse.sourceforge.net/io/aio.html</a> </li></ul><p>在Glibc AIO 的实现中, 用多线程同步来模拟 异步IO ,以上述代码为例,它牵涉了3个线程, 主线程(23908)新建 一个线程(23909)来调用 阻塞的pread函数,当pread返回时,又创建了一个线程(23910)来执行我们预设的异步回调函数, 23909 等待23910结束返回,然后23909也结束执行.. </p><p>实际上,为了避免线程的频繁创建、销毁,当有多个请求时,Glibc AIO 会使用线程池,但以上原理是不会变的,尤其要注意的是:我们的回调函数是在一个单独线程中执行的. </p><p>Glibc AIO 广受非议,存在一些难以忍受的缺陷和bug,饱受诟病,是极不推荐使用的. </p><p>详见:<a href="http://davmac.org/davpage/linux/async-io.html" target="_blank" rel="noopener">http://davmac.org/davpage/linux/async-io.html</a> </p><blockquote><p>目前linux上的AIO还不成熟,不推荐使用,那么jdk在linux上的aio是如何实现的呢?我们上文分析到,其实不是使用的linux aio,还是通过epoll实现的。</p></blockquote><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><ul><li>java异步,不高清楚里面的原理,很容易出错。我在编写本文demo时,就是没有对原理搞清楚,遇到好多问题。 </li><li>api看起来很简单,如果完全用异步,那就不简单了。比如客户端发送数据,我要使用一个CountDown才能发下一条,否则容易报错,因为底层一个channel不能同时发多份数据</li></ul>]]></content>
<summary type="html">
<h1 id="说明"><a href="#说明" class="headerlink" title="说明"></a>说明</h1><p>Java AIO就是Java作为对异步IO提供支持的NIO.2 ,Java NIO2 (JSR 203)定义了更多的 New I/O APIs, 提案2003提出,直到2011年才发布, 最终在JDK 7中才实现。</p>
<p>从编程模式上来看AIO相对于NIO的区别在于,NIO需要使用者线程不停的轮询IO对象,来确定是否有数据准备好可以读了,而AIO则是在数据准备好之后,才会通知数据使用者,这样使用者就不需要不停地轮询了。</p>
<p>当然AIO的异步特性并不是Java实现的伪异步,而是使用了系统底层API的支持,在Unix系统下,采用了epoll IO模型,而windows便是使用了IOCP模型。</p>
</summary>
<category term="java" scheme="http://along101.site/categories/java/"/>
<category term="java" scheme="http://along101.site/tags/java/"/>
<category term="socket" scheme="http://along101.site/tags/socket/"/>
<category term="aio" scheme="http://along101.site/tags/aio/"/>
</entry>
<entry>
<title>java nio socket 原理分析</title>
<link href="http://along101.site/2018/01/22/java-nio-socket/"/>
<id>http://along101.site/2018/01/22/java-nio-socket/</id>
<published>2018-01-22T17:05:57.000Z</published>
<updated>2018-07-20T03:10:04.405Z</updated>
<content type="html"><![CDATA[<h1 id="java-nio简介"><a href="#java-nio简介" class="headerlink" title="java nio简介"></a>java nio简介</h1><p>java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。</p><a id="more"></a><h1 id="使用demo"><a href="#使用demo" class="headerlink" title="使用demo"></a>使用demo</h1><h2 id="server端代码"><a href="#server端代码" class="headerlink" title="server端代码"></a>server端代码</h2><figure class="highlight plain"><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><span class="line">ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();</span><br><span class="line">serverSocketChannel.configureBlocking(false);</span><br><span class="line">serverSocketChannel.socket().bind(new InetSocketAddress(port));</span><br><span class="line"></span><br><span class="line">Selector selector = Selector.open();</span><br><span class="line">serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line"></span><br><span class="line">while (true) {</span><br><span class="line"> selector.select();</span><br><span class="line"> Set<SelectionKey> selectionKeys = selector.selectedKeys();</span><br><span class="line"> System.out.println(selectionKeys.size());</span><br><span class="line"> Iterator<SelectionKey> iterator = selectionKeys.iterator();</span><br><span class="line"> while (iterator.hasNext()) {</span><br><span class="line"> SelectionKey selectionKey = iterator.next();</span><br><span class="line"> // 删除已选的key 以防重复处理</span><br><span class="line"> iterator.remove();</span><br><span class="line"> if (selectionKey.isAcceptable()) {//如果有客户端连接进来</span><br><span class="line"> // 先拿到这个SelectionKey里面的ServerSocketChannel。</span><br><span class="line"> ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();</span><br><span class="line"> // 获得和客户端连接的通道</span><br><span class="line"> SocketChannel socketChannel = serverSocketChannel1.accept();</span><br><span class="line"> socketChannel.configureBlocking(false);//将此通道设置为非阻塞</span><br><span class="line"></span><br><span class="line"> //为了接收客户端发送过来的数据,需要将此通道绑定到选择器上,并为该通道注册读事件</span><br><span class="line"> socketChannel.register(selector, SelectionKey.OP_READ);</span><br><span class="line"> } else if (selectionKey.isReadable()) {//客户端发送数据过来了</span><br><span class="line"> //先拿到这个SelectionKey里面的SocketChannel。</span><br><span class="line"> SocketChannel socketChannel = (SocketChannel) selectionKey.channel();</span><br><span class="line"> </span><br><span class="line"> ByteBuffer buf = ByteBuffer.allocate(100);</span><br><span class="line"> try {</span><br><span class="line"> if (socketChannel.read(buf) <= 0) {</span><br><span class="line"> return;</span><br><span class="line"> }</span><br><span class="line"> byte[] receivedData = buf.array();</span><br><span class="line"> String msg = new String(receivedData).trim();</span><br><span class="line"> System.out.println("receivedData:" + msg);</span><br><span class="line"> buf.clear();</span><br><span class="line"> socketChannel.write(ByteBuffer.wrap("收到信息!!!".getBytes()));</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure><h2 id="client端代码"><a href="#client端代码" class="headerlink" title="client端代码"></a>client端代码</h2><figure class="highlight plain"><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><span class="line">socketChannel = SocketChannel.open(new InetSocketAddress(this.host, this.port));</span><br><span class="line">socketChannel.configureBlocking(false);</span><br><span class="line"></span><br><span class="line">// 打开并注册选择器到信道</span><br><span class="line">Selector selector = Selector.open();</span><br><span class="line">socketChannel.register(selector, SelectionKey.OP_READ);</span><br><span class="line"></span><br><span class="line"> while (true) {</span><br><span class="line"> try {</span><br><span class="line"> selector.select();</span><br><span class="line"> Set<SelectionKey> set = selector.selectedKeys();</span><br><span class="line"> Iterator<SelectionKey> ite = set.iterator();</span><br><span class="line"></span><br><span class="line"> while (ite.hasNext()) {</span><br><span class="line"> SelectionKey selectionKey = ite.next();</span><br><span class="line"> ite.remove(); //删除已选的key,以防重复处理</span><br><span class="line"> if (selectionKey.isConnectable()) {//看是否有连接发生</span><br><span class="line"> SocketChannel socketChannel = (SocketChannel) selectionKey.channel();</span><br><span class="line"> //如果正在连接,则完成连接</span><br><span class="line"> if (socketChannel.isConnectionPending()) {</span><br><span class="line"> socketChannel.finishConnect();</span><br><span class="line"> }</span><br><span class="line"> socketChannel.configureBlocking(false);//设置为非阻塞模式</span><br><span class="line"> //给服务器端发送数据</span><br><span class="line"> System.out.println("客户端连接上了服务器端。。。。");</span><br><span class="line"> //为了接收来自服务器端的数据,将此通道注册到选择器中</span><br><span class="line"> socketChannel.register(selector, SelectionKey.OP_READ);</span><br><span class="line"> } else if (selectionKey.isReadable()) {</span><br><span class="line"> SocketChannel socketChannel = (SocketChannel) selectionKey.channel();</span><br><span class="line"> //接收来自于服务器端发送过来的数据</span><br><span class="line"> ByteBuffer buf = ByteBuffer.allocate(1024);</span><br><span class="line"> socketChannel.read(buf);</span><br><span class="line"> byte[] receiveData = buf.array();</span><br><span class="line"> String msg = new String(receiveData).trim();</span><br><span class="line"> System.out.println("接收来自服务器端的数据为:" + msg);</span><br><span class="line"> buf.clear();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> } catch (Exception e) {</span><br><span class="line"> e.printStackTrace();</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure><h1 id="java-nio与io编程区别"><a href="#java-nio与io编程区别" class="headerlink" title="java nio与io编程区别"></a>java nio与io编程区别</h1><ul><li>io是基于流stream编程的,流没有读完或者写完,线程会阻塞</li><li>nio是基于缓冲区编程的,通过selector获取准备好的channel,从channel中获取数据到缓冲区,然后处理缓冲区数据。从select获取准备好的channel是一种通知机制。</li></ul><p>基于流编程是一个线程将流处理完,处理起来比较简单,但是向网络编程这种,多个连接的数据传输,就会多个线程,线程多就会增加线程切换带来的性能损耗。</p><p>基于nio的网络编程,使用一个selector来管理这些连接,当有事件发生时再处理。可以通过与cpu核心数相同大小的线程池来处理事件,这样就会减少线程切换。但是带来更加复杂的编程逻辑,因为缓冲区有大小限制,需要自行处理大数据传输缓冲区数据的拼接。</p><p>简单的说,selector的原理是使用一个selector来管理多个socket,当socket上有事件发生是,selector会把这个socket找出来,进行io处理。</p><p>用一张图,简单的描述io 和 nio区别</p><p><img src="/2018/01/22/java-nio-socket/1.png" alt=""></p><h1 id="java-nio-socket-selector模型原理"><a href="#java-nio-socket-selector模型原理" class="headerlink" title="java nio socket selector模型原理"></a>java nio socket selector模型原理</h1><p>要使用selector模型,需要将channel注册到selector上,服务端和客户端注册方式分别为:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);</span><br><span class="line">socketChannel.register(selector, SelectionKey.OP_READ);</span><br></pre></td></tr></table></figure></p><p><code>register</code>方法有两个重载的方法,返回一个<code>SelectionKey</code><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">public final SelectionKey register(Selector sel, int ops)</span><br><span class="line">public abstract SelectionKey register(Selector sel, int ops, Object att)</span><br></pre></td></tr></table></figure></p><p>第二个方法多一个att参数,可以注册时增加一个附加的对象,用返回的<code>SelectionKey</code>可以设置这个att,注意这个att,非常有用,可以放需要的任意对象。</p><p>注册得到的这个<code>SelectionKey</code>是selector模型中的关键,我们看看SelectionKeyImpl的源代码<br><figure class="highlight plain"><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><span class="line">public class SelectionKeyImpl extends AbstractSelectionKey {</span><br><span class="line"> final SelChImpl channel;</span><br><span class="line"> public final SelectorImpl selector;</span><br><span class="line"> private int index;</span><br><span class="line"> private volatile int interestOps;</span><br><span class="line"> private int readyOps;</span><br><span class="line">。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><ul><li><code>SelectorImpl</code> selector是所注册的选择器</li><li><code>interestOps</code>是所关心的操作事件集合,是一个int类型,使用二进制的位来组合不同时间集合</li><li><code>readyOps</code>是准备好的操作事件</li></ul><p>在channel上有四种:<br><figure class="highlight plain"><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><span class="line">public static final int OP_READ = 1 << 0;//可读</span><br><span class="line">public static final int OP_WRITE = 1 << 2;//可写</span><br><span class="line">public static final int OP_CONNECT = 1 << 3;//准备好连接</span><br><span class="line">public static final int OP_ACCEPT = 1 << 4;//serverSoket接收连接</span><br></pre></td></tr></table></figure></p><p><code>SelectionKeyImpl</code> 的父类<code>SelectionKey</code>有一个属性<code>attachment</code> ,就是前文我们提到的注册时可以传入的对象。<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">private volatile Object attachment = null;</span><br></pre></td></tr></table></figure></p><p><code>SelectionKey</code>还提供两个方法<code>public final Object attach(Object ob)</code>和<code>public final Object attachment()</code>,或可以设置和获取<code>attachment</code>。</p><p><code>SelectionKeyImpl</code> 的属性channel是<code>SelChImpl</code>类型,是所有<code>Selectable Channel</code>的接口</p><p><img src="/2018/01/22/java-nio-socket/2.png" alt=""></p><p><code>SelectionKeyImpl</code>是一个接口,源代码如下:<br><figure class="highlight plain"><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><span class="line">public interface SelChImpl extends Channel {</span><br><span class="line"> FileDescriptor getFD();</span><br><span class="line"> int getFDVal();</span><br><span class="line"> boolean translateAndUpdateReadyOps(int var1, SelectionKeyImpl var2);</span><br><span class="line"> boolean translateAndSetReadyOps(int var1, SelectionKeyImpl var2);</span><br><span class="line"> void translateAndSetInterestOps(int var1, SelectionKeyImpl var2);</span><br><span class="line"> int validOps();</span><br><span class="line"> void kill() throws IOException;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>在看看实现类<code>SocketChannel</code>的源代码:<br><figure class="highlight plain"><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><span class="line">class SocketChannelImpl extends SocketChannel implements SelChImpl {</span><br><span class="line"> private static NativeDispatcher nd;</span><br><span class="line"> private final FileDescriptor fd;</span><br><span class="line"> private final int fdVal;</span><br><span class="line"> private volatile long readerThread = 0L;</span><br><span class="line"> private volatile long writerThread = 0L;</span><br><span class="line"> private final Object readLock = new Object();</span><br><span class="line"> private final Object writeLock = new Object();</span><br><span class="line"> private final Object stateLock = new Object();</span><br><span class="line"> private boolean isReuseAddress;</span><br><span class="line"> private static final int ST_UNINITIALIZED = -1;</span><br><span class="line"> private static final int ST_UNCONNECTED = 0;</span><br><span class="line"> private static final int ST_PENDING = 1;</span><br><span class="line"> private static final int ST_CONNECTED = 2;</span><br><span class="line"> private static final int ST_KILLPENDING = 3;</span><br><span class="line"> private static final int ST_KILLED = 4;</span><br><span class="line"> private int state = -1;</span><br><span class="line"> private InetSocketAddress localAddress;</span><br><span class="line"> private InetSocketAddress remoteAddress;</span><br><span class="line"> private boolean isInputOpen = true;</span><br><span class="line"> private boolean isOutputOpen = true;</span><br><span class="line"> private boolean readyToConnect = false;</span><br><span class="line"> private Socket socket;</span><br><span class="line">。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里有个很重要的属性,<code>FileDescriptor</code> fd,就是操作系统对socket的文件描述符,可以再看看<code>SocketImpl</code>的源代码,也有个<code>FileDescriptor</code> fd属性,这里是同一个。</p><p>文件描述符是一个简单的整数,用以标明每一个被进程所打开的文件和socket。可以理解为一个数组的索引,数组里面放着文件和socket相关的信息。</p><p>了解了<code>SelectionKey</code>和<code>Channel</code>的结构,我们再来看看注册到selector的过程。channel注册到selector是调用的<code>AbstractSelectableChannel.register</code>方法:<br><figure class="highlight plain"><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><span class="line">public final SelectionKey register(Selector sel, int ops, Object att)</span><br><span class="line"> throws ClosedChannelException</span><br><span class="line">{</span><br><span class="line"> synchronized (regLock) {</span><br><span class="line"> //先校验</span><br><span class="line"> if (!isOpen())</span><br><span class="line"> throw new ClosedChannelException();</span><br><span class="line"> if ((ops & ~validOps()) != 0)</span><br><span class="line"> throw new IllegalArgumentException();</span><br><span class="line"> if (blocking)</span><br><span class="line"> throw new IllegalBlockingModeException();</span><br><span class="line"> //在本地keys数组中找SelectionKey,如果找到了,直接设置关心的操作和att对象</span><br><span class="line"> SelectionKey k = findKey(sel);</span><br><span class="line"> if (k != null) {</span><br><span class="line"> k.interestOps(ops);</span><br><span class="line"> k.attach(att);</span><br><span class="line"> }</span><br><span class="line"> //没有找到,新注册SelectionKey ,并放到本地数组中缓存</span><br><span class="line"> if (k == null) {</span><br><span class="line"> // New registration</span><br><span class="line"> synchronized (keyLock) {</span><br><span class="line"> if (!isOpen())</span><br><span class="line"> throw new ClosedChannelException();</span><br><span class="line"> k = ((AbstractSelector)sel).register(this, ops, att);</span><br><span class="line"> addKey(k);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return k;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>新注册<code>SelectionKey</code> 是调用selector的<code>register</code>方法:<br><figure class="highlight plain"><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><span class="line">protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3) {</span><br><span class="line"> if (!(var1 instanceof SelChImpl)) {</span><br><span class="line"> throw new IllegalSelectorException();</span><br><span class="line"> } else {</span><br><span class="line"> //新建一个SelectionKeyImpl ,传入channel,和seletecor,设置attachment,设置关心的操作</span><br><span class="line"> SelectionKeyImpl var4 = new SelectionKeyImpl((SelChImpl)var1, this);</span><br><span class="line"> var4.attach(var3);</span><br><span class="line"> Set var5 = this.publicKeys;</span><br><span class="line"> synchronized(this.publicKeys) {</span><br><span class="line"> //调用SelectorImpl的抽象方法implRegister注册SelectionKeyImpl </span><br><span class="line"> this.implRegister(var4);</span><br><span class="line"> }</span><br><span class="line"> var4.interestOps(var2);</span><br><span class="line"> return var4;</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>implRegister</code>根据操作系统有不同的实现。</p><h2 id="windows上的实现"><a href="#windows上的实现" class="headerlink" title="windows上的实现"></a>windows上的实现</h2><p>我们先看看windows下如何实现的。<br><figure class="highlight plain"><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><span class="line">SelectorImpl在windows下子类是WindowsSelectorImpl,implRegister方法代码如下:</span><br><span class="line"> protected void implRegister(SelectionKeyImpl var1) {</span><br><span class="line"> Object var2 = this.closeLock;</span><br><span class="line"> synchronized(this.closeLock) {</span><br><span class="line"> if (this.pollWrapper == null) {</span><br><span class="line"> throw new ClosedSelectorException();</span><br><span class="line"> } else {</span><br><span class="line"> this.growIfNeeded();</span><br><span class="line"> this.channelArray[this.totalChannels] = var1;</span><br><span class="line"> var1.setIndex(this.totalChannels);</span><br><span class="line"> this.fdMap.put(var1);</span><br><span class="line"> this.keys.add(var1);</span><br><span class="line"> this.pollWrapper.addEntry(this.totalChannels, var1);</span><br><span class="line"> ++this.totalChannels;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>这里把<code>SelectionKey</code>加到<code>SelectorImpl</code>中的好多地方<code>channelArray</code>、<code>fdMap</code>、<code>keys</code>、<code>pollWrapper</code>。加到每个地方的作用,我们先不管,后面再看,反正是保存在属性里面了。有个问题,连接关闭了的话,什么时候释放呢? 后面我们会分析到。</p><p>从<code>selector.select()</code>方法往里面跟,跟到<code>SelectorImpl</code>的:<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">private int lockAndDoSelect(long var1) throws IOException</span><br></pre></td></tr></table></figure></p><p>再跟到<code>WindowsSelectorImpl</code>的<br><figure class="highlight plain"><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><span class="line">protected int doSelect(long var1) throws IOException {</span><br><span class="line">。。。</span><br><span class="line"> //先处理注销的key,通过selector调用cancel方法注销selectionKey</span><br><span class="line"> this.timeout = var1;</span><br><span class="line"> this.processDeregisterQueue();</span><br><span class="line"> if (this.interruptTriggered) {</span><br><span class="line"> this.resetWakeupSocket();</span><br><span class="line"> return 0;</span><br><span class="line">。。。</span><br><span class="line"> try {</span><br><span class="line"> //这里阻塞住</span><br><span class="line"> this.subSelector.poll();</span><br><span class="line"> } catch (IOException var7) {</span><br><span class="line"> this.finishLock.setException(var7);</span><br><span class="line"> }</span><br><span class="line">。。。</span><br><span class="line"> this.finishLock.checkForException();</span><br><span class="line"> this.processDeregisterQueue();</span><br><span class="line"> //更新SelectedKeys</span><br><span class="line"> int var3 = this.updateSelectedKeys();</span><br><span class="line"> this.resetWakeupSocket();</span><br><span class="line"> return var3;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>重要的两行代码是<code>this.subSelector.poll();</code>和 <code>this.updateSelectedKeys();</code></p><p>先看<code>subSelector.poll()</code>,跟到<br><figure class="highlight plain"><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><span class="line">private int poll() throws IOException {</span><br><span class="line"> return this.poll0(WindowsSelectorImpl.this.pollWrapper.pollArrayAddress, Math.min(WindowsSelectorImpl.this.totalChannels, 1024), this.readFds, this.writeFds, this.exceptFds, WindowsSelectorImpl.this.timeout);</span><br><span class="line">}</span><br><span class="line">private native int poll0(long var1, int var3, int[] var4, int[] var5, int[] var6, long var7);</span><br></pre></td></tr></table></figure></p><p>这里<code>poll0</code>是一个native方法,再往下跟就需要看jdk的源代码了。</p><p>简单的解释下,这里用到了<code>pollWrapper</code>,我们回头看看之前注册的代码<code>this.pollWrapper.addEntry(this.totalChannels, var1);</code>这里面实际上是将<code>SelectionKey</code>的fd值放在<code>pollWrapper</code>内部的数组中,可参考<code>PollArrayWrapper</code>的代码。</p><p><code>poll0()</code>方法第一个参数为fd开始的地址,第二个是fd数量,后面三个fd数组分别是:保存发生read的FD,保存发生write的FD ,保存发生except(错误)的FD ,最后一个是超时时间。poll0()会监听pollWrapper中的FD有没有数据进出,这会造成IO阻塞,直到有数据读写事件发生,然后将发生数据的分别保存在read/write/except数组中。</p><p>poll到发生事件的fd后,需要将找到这些fd对应的<code>SelectionKey</code>,通过<code>updateSelectedKeys</code>实现的:<br><figure class="highlight plain"><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><span class="line">private int updateSelectedKeys() {</span><br><span class="line"> ++this.updateCount;</span><br><span class="line"> byte var1 = 0;</span><br><span class="line"> int var4 = var1 + this.subSelector.processSelectedKeys(this.updateCount);</span><br><span class="line"> WindowsSelectorImpl.SelectThread var3;</span><br><span class="line"> for(Iterator var2 = this.threads.iterator(); var2.hasNext(); var4 += var3.subSelector.processSelectedKeys(this.updateCount)) {</span><br><span class="line"> var3 = (WindowsSelectorImpl.SelectThread)var2.next();</span><br><span class="line"> }</span><br><span class="line"> return var4;</span><br><span class="line">}</span><br><span class="line"> private int processSelectedKeys(long var1) {</span><br><span class="line"> byte var3 = 0;</span><br><span class="line"> int var4 = var3 + this.processFDSet(var1, this.readFds, Net.POLLIN, false);</span><br><span class="line"> var4 += this.processFDSet(var1, this.writeFds, Net.POLLCONN | Net.POLLOUT, false);</span><br><span class="line"> var4 += this.processFDSet(var1, this.exceptFds, Net.POLLIN | Net.POLLCONN | Net.POLLOUT, true);</span><br><span class="line"> return var4;</span><br><span class="line"> }</span><br></pre></td></tr></table></figure></p><p>在看看<code>processFDSet</code>方法:<br><figure class="highlight plain"><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><span class="line">private int processFDSet(long var1, int[] var3, int var4, boolean var5) {</span><br><span class="line"> 。。。</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>代码有点多,主要是根据前面保存三类fd数组找出<code>selectionKey</code>,加到<code>selectedKeys</code>中。前面注册的时候,将<code>SelectionKey</code>放到fdMap中,这里就从<code>fdMap</code>中根据fd找到<code>SelectionKey</code>。</p><p>后续通过<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Set<SelectionKey> set = selector.selectedKeys();</span><br></pre></td></tr></table></figure></p><p>得到的<code>SelectionKey</code>集合就是这个这里加进来的,注意,我们遍历set时,拿出一个key,就要从set中删除掉,避免重复消费。</p><p>后面就是根据这个<code>SelectionKey</code>获取到<code>SocketChannel</code>,从<code>SocketChannel</code>中读取数据到<code>ByteBuffer</code>,处理业务逻辑。</p><p>到这里,我们就把channel注册到selector,到selector如何取到有事件发生的channel的过程分析完了,接下来我们看看通道关闭时,如何从selector中注销的。</p><p>从<code>socketChannel.close()</code>开始,找到<code>implCloseChannel()</code>,找到<code>AbstractSelectableChannel.implCloseChannel</code>:<br><figure class="highlight plain"><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><span class="line">protected final void implCloseChannel() throws IOException {</span><br><span class="line"> implCloseSelectableChannel();</span><br><span class="line"> synchronized (keyLock) {</span><br><span class="line"> int count = (keys == null) ? 0 : keys.length;</span><br><span class="line"> for (int i = 0; i < count; i++) {</span><br><span class="line"> SelectionKey k = keys[i];</span><br><span class="line"> if (k != null)</span><br><span class="line"> k.cancel();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p><code>implCloseSelectableChannel()</code>先关闭chanel,然后逐个调用 <code>k.cancel()</code>取消<code>SelectionKey</code>,在往<code>k.cancel()</code>方法里面跟:<br><figure class="highlight plain"><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><span class="line">public final void cancel() {</span><br><span class="line"> synchronized (this) {</span><br><span class="line"> if (valid) {</span><br><span class="line"> valid = false;</span><br><span class="line"> ((AbstractSelector)selector()).cancel(this);</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>调用<code>selector</code>的<code>cancel</code>方法,再跟进去<br><figure class="highlight plain"><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><span class="line">void cancel(SelectionKey k) { </span><br><span class="line"> synchronized (cancelledKeys) {</span><br><span class="line"> cancelledKeys.add(k);</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>将需要关闭的key加入到<code>cancelledKeys</code>集合中。啥时候处理这些关闭的key呢?先在<code>AbstractSelector</code>类中找<code>cancelledKeys</code>的用处,发现没有,再找子类<code>SelectorImpl</code>,发现:<br><figure class="highlight plain"><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><span class="line">void processDeregisterQueue() throws IOException {</span><br><span class="line"> Set var1 = this.cancelledKeys();</span><br><span class="line"> synchronized(var1) {</span><br><span class="line"> if (!var1.isEmpty()) {</span><br><span class="line"> Iterator var3 = var1.iterator();</span><br><span class="line"> while(var3.hasNext()) {</span><br><span class="line"> SelectionKeyImpl var4 = (SelectionKeyImpl)var3.next();</span><br><span class="line"> try {</span><br><span class="line"> this.implDereg(var4);</span><br><span class="line"> } catch (SocketException var11) {</span><br><span class="line"> throw new IOException("Error deregistering key", var11);</span><br><span class="line"> } finally {</span><br><span class="line"> var3.remove();</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>这里会调用<code>implDereg</code>实现注销<code>SelectionKey</code>,再找<code>processDeregisterQueue</code>方法被调用的地方,前文中我们已经分析过的代码,在<code>WindowsSelectorImpl.doSelect</code>里面会先调用<code>processDeregisterQueue</code>。</p><p>分析到这里,windows上的select的整个过程就完成了。</p><h2 id="linux上的实现"><a href="#linux上的实现" class="headerlink" title="linux上的实现"></a>linux上的实现</h2><p>下面我们分析下linux下是如何实现。</p><p>我们用jd-gui打开linux下的rt.jar,也可以直接在linux下用eclipse或者idea打开源代码。</p><p>从这行代码开始往下跟,看看获取的<code>Selector</code>是哪个类<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">Selector selector = Selector.open();</span><br></pre></td></tr></table></figure></p><p>跟到:<br><figure class="highlight plain"><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><span class="line">public static Selector open() throws IOException {</span><br><span class="line"> return SelectorProvider.provider().openSelector();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>默认情况下使用<code>DefaultSelectorProvider</code><br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SelectorProvider.access$102(DefaultSelectorProvider.create());</span><br></pre></td></tr></table></figure></p><p>再跟进<code>sun.nio.ch.DefaultSelectorProvider.create</code>方法:<br><figure class="highlight plain"><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><span class="line">public static SelectorProvider create()</span><br><span class="line">{</span><br><span class="line"> String str = (String)AccessController.doPrivileged(new GetPropertyAction("os.name"));</span><br><span class="line"> if (str.equals("SunOS")) {</span><br><span class="line"> return createProvider("sun.nio.ch.DevPollSelectorProvider");</span><br><span class="line"> }</span><br><span class="line"> if (str.equals("Linux")) {</span><br><span class="line"> return createProvider("sun.nio.ch.EPollSelectorProvider");</span><br><span class="line"> }</span><br><span class="line"> return new PollSelectorProvider();</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>可以看到,操作系统名字为Linux时,直接使用<code>EPollSelectorProvider</code>,实际上<code>epoll</code>是<code>linux</code>内核2.6之后出现的。</p><p><code>EPollSelectorProvider</code>创建<code>EPollSelectorImpl</code>。再看<code>EPollSelectorImpl</code>中注册<code>SelectionKey</code>的代码:<br><figure class="highlight plain"><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><span class="line">protected void implRegister(SelectionKeyImpl paramSelectionKeyImpl)</span><br><span class="line">{</span><br><span class="line"> if (this.closed) {</span><br><span class="line"> throw new ClosedSelectorException();</span><br><span class="line"> }</span><br><span class="line"> SelChImpl localSelChImpl = paramSelectionKeyImpl.channel;</span><br><span class="line"> int i = Integer.valueOf(localSelChImpl.getFDVal()).intValue();</span><br><span class="line"> this.fdToKey.put(Integer.valueOf(i), paramSelectionKeyImpl);</span><br><span class="line"> this.pollWrapper.add(i);</span><br><span class="line"> this.keys.add(paramSelectionKeyImpl);</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>又是把<code>SelectionKey</code>加到<code>SelectorImpl</code>中,包括<code>fdToKey</code>、<code>pollWrapper</code>、<code>keys</code>。</p><p>我们先看看选择器是如何选择这些key的。<br><figure class="highlight plain"><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><span class="line">protected int doSelect(long paramLong) throws IOException{</span><br><span class="line"> if (this.closed)</span><br><span class="line"> throw new ClosedSelectorException();</span><br><span class="line"> processDeregisterQueue();</span><br><span class="line"> try {</span><br><span class="line"> begin();</span><br><span class="line"> this.pollWrapper.poll(paramLong);</span><br><span class="line"> } finally {</span><br><span class="line"> end();</span><br><span class="line"> }</span><br><span class="line"> processDeregisterQueue();</span><br><span class="line"> int i = updateSelectedKeys();</span><br><span class="line"> if (this.pollWrapper.interrupted()){</span><br><span class="line"> this.pollWrapper.putEventOps(this.pollWrapper.interruptedIndex(), 0);</span><br><span class="line"> synchronized (this.interruptLock) {</span><br><span class="line"> this.pollWrapper.clearInterrupted();</span><br><span class="line"> IOUtil.drain(this.fd0);</span><br><span class="line"> this.interruptTriggered = false;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return i;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>跟windows的代码有点类似,可以看到重点是调用了<code>this.pollWrapper.poll(paramLong);</code></p><p>再来看<code>EPollArrayWrapper.poll</code>方法:<br><figure class="highlight plain"><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><span class="line">int poll(long paramLong) throws IOException {</span><br><span class="line"> updateRegistrations();</span><br><span class="line"> this.updated = epollWait(this.pollArrayAddress, NUM_EPOLLEVENTS, paramLong, this.epfd);</span><br><span class="line"> for (int i = 0; i < this.updated; i++) {</span><br><span class="line"> if (getDescriptor(i) == this.incomingInterruptFD)</span><br><span class="line"> {</span><br><span class="line"> this.interruptedIndex = i;</span><br><span class="line"> this.interrupted = true;</span><br><span class="line"> break;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return this.updated;</span><br><span class="line">}</span><br><span class="line">private native int epollWait(long paramLong1, int paramInt1, long paramLong2, int paramInt2) throws IOException;</span><br></pre></td></tr></table></figure></p><p>这里调用了本地的<code>epollWait</code>方法,大概解释下,第一个参数是拉出事件存放fd数组的地址,第二个是最大fd个数,第三个是超时时间,最后一个是epoll实例的fd,通过<code>epoll_create</code>创建的。</p><p><code>epollWait</code>方法会等待注册在epoll实例上的fd的事件,有事件发生,会将这些fd放在<code>pollArrayAddress</code>指向的数组中,返回发生事件的fd个数。</p><p>我们再回到<code>EPollSelectorImpl</code>的<code>doSelect</code>方法,poll到事件,将事件对应的fd放在数组里面,然后调用<code>updateSelectedKeys</code>方法,根据fd值找到<code>SelectionKey</code>值<br><figure class="highlight plain"><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><span class="line">private int updateSelectedKeys()</span><br><span class="line">{</span><br><span class="line"> int i = this.pollWrapper.updated;</span><br><span class="line"> int j = 0;</span><br><span class="line"> for (int k = 0; k < i; k++)</span><br><span class="line"> {</span><br><span class="line"> int m = this.pollWrapper.getDescriptor(k);</span><br><span class="line"> SelectionKeyImpl localSelectionKeyImpl = (SelectionKeyImpl)this.fdToKey.get(Integer.valueOf(m));</span><br><span class="line"> if (localSelectionKeyImpl != null)</span><br><span class="line"> {</span><br><span class="line"> int n = this.pollWrapper.getEventOps(k);</span><br><span class="line"> if (this.selectedKeys.contains(localSelectionKeyImpl))</span><br><span class="line"> {</span><br><span class="line"> if (localSelectionKeyImpl.channel.translateAndSetReadyOps(n, localSelectionKeyImpl)) {</span><br><span class="line"> j++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> else</span><br><span class="line"> {</span><br><span class="line"> localSelectionKeyImpl.channel.translateAndSetReadyOps(n, localSelectionKeyImpl);</span><br><span class="line"> if ((localSelectionKeyImpl.nioReadyOps() & localSelectionKeyImpl.nioInterestOps()) != 0)</span><br><span class="line"> {</span><br><span class="line"> this.selectedKeys.add(localSelectionKeyImpl);</span><br><span class="line"> j++;</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> }</span><br><span class="line"> return j;</span><br><span class="line">}</span><br></pre></td></tr></table></figure></p><p>代码有点多,看关键的<br><figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">SelectionKeyImpl localSelectionKeyImpl = (SelectionKeyImpl)this.fdToKey.get(Integer.valueOf(m));</span><br></pre></td></tr></table></figure></p><p>从<code>fdToKey</code>中根据fd值拿到key,然后放到<code>selectedKeys</code>中。</p><p>到这里linux上的select实现模型也分析完了,注销方式跟windows上实现相同,不再描述。</p><h1 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h1><p>nio编程模型,主要是要搞清楚几个类之间的关系,<code>Selector</code>、<code>Channel</code>、<code>SelectionKey</code>,其中<code>SelectionKey</code>是<code>Selector</code>和<code>Channel</code>的纽带。</p><p><img src="/2018/01/22/java-nio-socket/3.png" alt=""></p><ul><li>一个<code>SelectableChannel</code>中包含多个<code>SelectKey</code>,是通过注册不同的<code>Selector</code>来实现的。</li><li>一个<code>Selector</code>中包含多个<code>SelectionKey</code>,每个<code>key</code>对应一个<code>channel</code></li><li>一个<code>SelectionKey</code>只包含一个<code>channel</code>和一个<code>selector</code></li></ul><p>也就是<code>channel</code>和<code>Selector</code>是多对多的关系,通过<code>SelectionKey</code>来关联。</p><p><code>SelectionKey</code>还可以设置一个<code>attachment</code>的附加对象,用于处理特定的业务逻辑。</p><h1 id="参考资源"><a href="#参考资源" class="headerlink" title="参考资源"></a>参考资源</h1><p><a href="https://www.cnblogs.com/Anker/p/3265058.html" target="_blank" rel="noopener">https://www.cnblogs.com/Anker/p/3265058.html</a> poll/select/epoll区别讲的很清晰</p>]]></content>
<summary type="html">
<h1 id="java-nio简介"><a href="#java-nio简介" class="headerlink" title="java nio简介"></a>java nio简介</h1><p>java.nio全称java non-blocking IO,是指jdk1.4 及以上版本里提供的新api(New IO) ,为所有的原始类型(boolean类型除外)提供缓存支持的数据容器,使用它可以提供非阻塞式的高伸缩性网络。</p>
</summary>
<category term="java" scheme="http://along101.site/categories/java/"/>
<category term="java" scheme="http://along101.site/tags/java/"/>
<category term="nio" scheme="http://along101.site/tags/nio/"/>
<category term="socket" scheme="http://along101.site/tags/socket/"/>
</entry>
</feed>