-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
3257 lines (2470 loc) · 198 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
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title><![CDATA[蹤影]]></title>
<link href="http://smlsun.com/atom.xml" rel="self"/>
<link href="http://smlsun.com/"/>
<updated>2014-03-31T17:57:11+08:00</updated>
<id>http://smlsun.com/</id>
<author>
<name><![CDATA[smlsun]]></name>
</author>
<generator uri="http://octopress.org/">Octopress</generator>
<entry>
<title type="html"><![CDATA[spring-boot: server startup or shutdown listener]]></title>
<link href="http://smlsun.com/blog/2014/03/31/spring-boot-start-shutdown/"/>
<updated>2014-03-31T00:00:00+08:00</updated>
<id>http://smlsun.com/blog/2014/03/31/spring-boot-start-shutdown</id>
<content type="html"><![CDATA[<p>在之前的專案中遇到一種狀況,使用者說他們的服務沒有反應?通常會有幾種情形:</p>
<ol>
<li>服務被人為關閉</li>
<li>作業系統被關閉</li>
<li>記憶體不足造成服務終止</li>
</ol>
<p>第三點的原因有很多,這邊先不談,關於 1、2 點可能是人為,或者管理者在做系統維護時,不知道需要在重新啟動服務,或是錯誤關閉,不管哪一種,都會造成服務無法運作,接著你就會接到電話或信件,系統不 work 了。</p>
<p>通常,也只能問有沒有人去操作主機,或是有沒有人去關閉服務?常常問不出所以然,當然就如同上面所舉的幾點,不一定是人為因素,但我們希望可以加快系統出現異常時的反應速度,將服務停止時間降到最低。</p>
<p>因此,主動通知是比較積極的作法,唯有當下接收到服務開啟或關閉的訊息,才能夠在最接近問題發生的點來釐清問題,另一方面,也是較有效率的作法,誰也不想定期每天去檢查你所安裝的服務今天是否有正常運行。</p>
<p>說這麼多,此篇要介紹的是如何在使用 spring-boot 時,定義 server 開啟或關閉時執行特定的程式,比如發 mail 或是寫 log,當然使用傳統 spring 也可以,參考:<a href="http://www.mkyong.com/spring/spring-postconstruct-and-predestroy-example/">Spring @PostConstruct And @PreDestroy Example</a>。</p>
<p>在實作上,我們可以利用 @Configuration 的特性,在類別中,只要我們打上 @Configuration,該類別就會作為類似 xml 設定檔,在服務啟動時將在類別中定義的方法根據每個方法的 annotation 註冊,下面將分別針對啟動與關閉服務利用 @PostConstruct,以及 @PreDestroy 來達成。</p>
<h2>服務啟動時 @PostConstruct</h2>
<p>@PostConstruct,官方 api 參考下面連結 <a href="http://docs.oracle.com/javaee/5/api/javax/annotation/PostConstruct.html">Annotation Type PostConstruct</a>,文件中有詳細說明使用時機,其中:</p>
<blockquote><p>The PostConstruct annotation is used on a method that needs to be executed after dependency injection is done to perform any initialization.</p></blockquote>
<p>可以看到執行的時機點在依賴注入後進行初始作業的執行,正是我們想要的。</p>
<h2>服務關閉時 @PreDestroy</h2>
<p>@PreDestroy,官方 api 參考下面連結 <a href="http://docs.oracle.com/javaee/5/api/javax/annotation/PreDestroy.html">Annotation Type PreDestroy</a></p>
<blockquote><p>The PreDestroy annotation is used on methods as a callback notification to signal that the instance is in the process of being removed by the container.</p></blockquote>
<p>文中說明,當執行實體被容器移除時會執行該動作,若我們將 @PreDestroy 定義在有 @Configuration 標註的類別中,因為 @Configuration 在服務啟動時只會產生一個實體,因此一旦服務被關閉時進行類別 GC 時,就會連帶觸動執行有標註 @PreDestroy 的函式。</p>
<p>透過上述兩個 annotation 就可以做到啟動或關閉時發出 mail 通知,不過這邊要注意關閉的情形,若使用者強制將服務關閉,不等待 server 的後續處理,那標註 @PreDestroy 的函式將無法完全執行完畢,目前個人沒有更好的方式可以避免,若讀者有不錯的作法,務必讓我知道,或者是關於上述應用情境有更好的處理方式,比如該 annotation 有錯用的地方,也請不吝指教。</p>
<p>最後附上開關 server 自動送出 mail 的程式碼給大家參考,以 gmail 為例:</p>
<p>定義 MailSender 以及預設的 SimpleMailMessage</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>import org.springframework.context.annotation.Bean;
</span><span class='line'>import org.springframework.context.annotation.Configuration;
</span><span class='line'>import org.springframework.mail.MailSender;
</span><span class='line'>import org.springframework.mail.SimpleMailMessage;
</span><span class='line'>import org.springframework.mail.javamail.JavaMailSenderImpl;
</span><span class='line'>
</span><span class='line'>import java.util.Properties;
</span><span class='line'>
</span><span class='line'>@Configuration
</span><span class='line'>public class ApplicationConfig{
</span><span class='line'>
</span><span class='line'> @Bean
</span><span class='line'> public MailSender mailSender(){
</span><span class='line'> JavaMailSenderImpl javaMailSenderImpl = new JavaMailSenderImpl();
</span><span class='line'> javaMailSenderImpl.setHost("smtp.gmail.com");
</span><span class='line'> javaMailSenderImpl.setPort(587);
</span><span class='line'> javaMailSenderImpl.setUsername("user");
</span><span class='line'> javaMailSenderImpl.setPassword("password");
</span><span class='line'>
</span><span class='line'> Properties mailProp = new Properties();
</span><span class='line'> mailProp.put("mail.smtp.auth", true);
</span><span class='line'> mailProp.put("mail.smtp.starttls.enable", true);
</span><span class='line'>
</span><span class='line'> javaMailSenderImpl.setJavaMailProperties(mailProp);
</span><span class='line'> return (MailSender) javaMailSenderImpl;
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'> @Bean
</span><span class='line'> public SimpleMailMessage simpleMailMessage() {
</span><span class='line'> SimpleMailMessage msg = new SimpleMailMessage();
</span><span class='line'> msg.setTo("[email protected]");
</span><span class='line'> msg.setFrom("[email protected]");
</span><span class='line'> msg.setText("內文");
</span><span class='line'> return msg;
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'>}
</span></code></pre></td></tr></table></div></figure>
<p>定義開關服務處理函式,將定義好的 MailSender 以及 SimpleMailMessage 注入並且使用:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>import org.springframework.beans.factory.annotation.Autowired;
</span><span class='line'>import org.springframework.context.annotation.Configuration;
</span><span class='line'>import org.springframework.mail.MailException;
</span><span class='line'>import org.springframework.mail.MailSender;
</span><span class='line'>import org.springframework.mail.SimpleMailMessage;
</span><span class='line'>
</span><span class='line'>import javax.annotation.PostConstruct;
</span><span class='line'>import javax.annotation.PreDestroy;
</span><span class='line'>
</span><span class='line'>@Configuration
</span><span class='line'>public class ContextListener {
</span><span class='line'>
</span><span class='line'> @Autowired
</span><span class='line'> MailSender mailSender;
</span><span class='line'>
</span><span class='line'> @Autowired
</span><span class='line'> SimpleMailMessage simpleMailMessage;
</span><span class='line'>
</span><span class='line'> @PostConstruct
</span><span class='line'> public void startupMailNotify(){
</span><span class='line'>
</span><span class='line'> simpleMailMessage.setSubject("服務啟動");
</span><span class='line'>
</span><span class='line'> try{
</span><span class='line'> mailSender.send(simpleMailMessage);
</span><span class='line'> }
</span><span class='line'> catch(MailException ex) {
</span><span class='line'> System.err.println(ex.getMessage());
</span><span class='line'> }
</span><span class='line'> }
</span><span class='line'> @PreDestroy
</span><span class='line'> public void shutdownMailNotify(){
</span><span class='line'> simpleMailMessage.setSubject("服務關閉");
</span><span class='line'> try{
</span><span class='line'> mailSender.send(simpleMailMessage);
</span><span class='line'> }
</span><span class='line'> catch(MailException ex) {
</span><span class='line'> System.err.println(ex.getMessage());
</span><span class='line'> }
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'>}
</span></code></pre></td></tr></table></div></figure>
<p>其實可以在精簡,不過也夠簡單了,並且降低耦合,是吧~</p>
<p>在 spring 與 spring-boot 其中之一的差別,就是 spring-boot 可以不需要 xml 定義,寫起來又更方便了點、精簡了點。</p>
<p><a rel="bookmark" href="http://smlsun.com/blog/2014/03/31/spring-boot-start-shutdown/"></a></p>]]></content>
</entry>
<entry>
<title type="html"><![CDATA[grails: 靜態資源或網址加入快取機制]]></title>
<link href="http://smlsun.com/blog/2014/03/31/grails-plugin-cache-head/"/>
<updated>2014-03-31T00:00:00+08:00</updated>
<id>http://smlsun.com/blog/2014/03/31/grails-plugin-cache-head</id>
<content type="html"><![CDATA[<p>關於瀏覽器的 cache 機制可以參考下列文章:</p>
<ul>
<li><a href="https://blog.othree.net/log/2012/12/22/cache-control-and-etag/">Cache Control 與 ETag</a></li>
<li><a href="http://blog.toright.com/posts/3414/%E5%88%9D%E6%8E%A2-http-1-1-cache-%E6%A9%9F%E5%88%B6">初探 HTTP 1.1 Cache 機制</a></li>
</ul>
<p>其中 <a href="https://blog.othree.net/log/2012/12/22/cache-control-and-etag/">Cache Control 與 ETag</a> 這編寫的蠻清楚的,可以清楚知道 Cache 的使用還有相關的屬性差別,基本上對於瀏覽器是否使用 cache 可以從時間的判斷,以及 etag(Entity Tag) 的判斷來決定 server 回傳的 http status 是否為 304,若為 304 則瀏覽器就會讀取 cache 而不會對 server 請求 request。</p>
<p>知道瀏覽器判斷使用 cache 的原理後,以 grails 為例,我們可以加入如下的判斷式:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>if (isRequestedFileModified(req)) {
</span><span class='line'> //get Program and return the image data
</span><span class='line'>} else {
</span><span class='line'> //file is the same
</span><span class='line'> response.sendStatus(304)
</span><span class='line'> return
</span><span class='line'>}
</span><span class='line'>
</span><span class='line'>def isRequestedFileModified(req) {
</span><span class='line'> // check etag and/or compare last modified date/time
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>如此,當我們對後端請求取得資源時,先檢查 etag 有沒有存在,若沒有表示為全新請求,若 etag 相同,可在進一步檢查有沒有更新過或是快取距離最後一次更新是否超過 max-age 所定義的時間。</p>
<p>在實際網站的開發,對於需要大量資源進行計算的請求,或是靜態圖片的讀取,就可以使用快取的機制,來減少 server 的負載,畢竟若不是經常更新的資料,我們可以不用每次都向 server 請求重新取得資源。</p>
<p>雖然上述範例是用 grails 為例,但對於其他語言的實作,不外乎就使檢查 etag 以及最後更新時間,另外若你是使用 grails 進行開發,可以直接使用下列 plugin:</p>
<ul>
<li><a href="http://grails.org/plugin/cache-headers">Caching Headers Plugin</a></li>
</ul>
<p>使用此 plugin 你可以事先定義各種 cache 機制的政策比如 plugin 中的範例:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>cache.headers.presets = [
</span><span class='line'> unauthed_page: [shared:true, validFor: 300], // 5 minute refresh window
</span><span class='line'> authed_page: false, // No caching for logged in user
</span><span class='line'> content: [shared:true, validFor: 3600], // 1hr on content
</span><span class='line'> recent_items_feed: [shared: true, validFor: 1800], // 30 minute throttle on RSS updates
</span><span class='line'> search_results: [validFor: 60, shared: true],
</span><span class='line'> taxonomy_results: [validFor: 60, shared: true]
</span><span class='line'>]</span></code></pre></td></tr></table></div></figure>
<p>以便根據不同狀況來有效使用 cache,實際在 controller 中定義 cache 所需屬性可以使用其提供的 DSL 來進行,如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>
</span><span class='line'>def object = s3Service.getObject("${grailsApplication.config.grails.aws.root}/${params.name}/${file}")
</span><span class='line'>
</span><span class='line'>withCacheHeaders {
</span><span class='line'> def image = object
</span><span class='line'> delegate.lastModified {
</span><span class='line'> image.getLastModifiedDate()
</span><span class='line'> }
</span><span class='line'> etag {
</span><span class='line'> image.getKey()
</span><span class='line'> }
</span><span class='line'> generate {
</span><span class='line'> response.contentType = "image/jpeg"
</span><span class='line'> response.outputStream << image.dataInputStream
</span><span class='line'> }
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>其中:</p>
<ol>
<li><code>delegate.lastModified</code> closure 定義比對時間的資料來源。</li>
<li><code>etag</code> closure 定義資源識別的方式。</li>
<li><code>generate</code> closure 會在判斷該請求需要重新取得進行呼叫,若不需要重新取的則會回傳 status 304,使用瀏覽器快取。</li>
</ol>
<blockquote><p>補充:若要了解上述 closure 的使用原理可參考下述連結,範例簡單清楚:</p>
<ul>
<li><a href="http://www.codedata.com.tw/java/groovy-tutorial-03-closure/">Groovy Tutorial(3)淺談 Closure 程式設計</a></li>
</ul>
<p>可以幫助更進一步理解運作原理。</p></blockquote>
<p>如此,若要自己撰寫快取機制,或是使用既有的 plugin 都可以在關鍵時刻讓你的網站能夠有更快的效能。</p>
<p><a rel="bookmark" href="http://smlsun.com/blog/2014/03/31/grails-plugin-cache-head/"></a></p>]]></content>
</entry>
<entry>
<title type="html"><![CDATA[grails: gorm 自動更新與 discard 方法 使用特性與注意事項]]></title>
<link href="http://smlsun.com/blog/2014/03/31/grails-gorm-discard/"/>
<updated>2014-03-31T00:00:00+08:00</updated>
<id>http://smlsun.com/blog/2014/03/31/grails-gorm-discard</id>
<content type="html"><![CDATA[<p>在使用 grails domain 時,有時候雖然我們有對 domain 變更值,但我們需要經過一些判斷後,才要正確寫入資料庫,但在 grails 的環境在你還沒有下 save 的指令時,他會因為函式執行的需要進行 auto save,關於 gorm 原理可以參考下列文章</p>
<p><a href="http://spring.io/blog/2010/06/23/gorm-gotchas-part-1/">GORM Gotchas (Part 1)</a></p>
<p>其中有一段如下:</p>
<blockquote><p> def b = Book.findByAuthor(params.author)
b.title = b.title.reverse()</p>
<p>Note that there is no call to save() here. When the request has completed you will find that the book’s title has been reversed in the database - the change has been persisted without an explicit save. This is because:</p>
<ol>
<li>the book is attached to the session (by virtue of being retrieved by a query);</li>
<li>the title property is persistent (all properties are persistent unless configured as transient); and</li>
<li>the property value has changed by the time the session closes.</li>
</ol>
</blockquote>
<p>上述說明了就算沒有下 save() 的指令,特定的狀況下 domain 還是有可能自動寫入資料庫。</p>
<p>如果我們不想要自動更新怎麼辦?文中也有提到下述狀況將放棄自動更新:</p>
<blockquote><p>if any of the property values fail validation, the changes will not be persisted. Of course, if the values are valid and yet you still don’t want to persist them, you can call <code>discard()</code> on your instance. This won’t reset the values of the instance’s properties, but it will ensure that they aren’t saved to the database.</p></blockquote>
<p>當 validation 不通過,或是執行 <code>discard()</code> 將取消更新。</p>
<p>對於 grails domain 自動儲存以及取消自動儲存的特性有所了解後,
筆者目前遇到的狀況如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def purchaseSheetDet = PurchaseSheetDet.get(params.id)
</span><span class='line'>purchaseSheetDet.properties = params
</span><span class='line'>
</span><span class='line'>
</span><span class='line'>def batch = Batch.findByName(params["batch.name"])
</span><span class='line'>purchaseSheetDet.batch = batch
</span><span class='line'>
</span><span class='line'>purchaseSheetDet.discard()
</span></code></pre></td></tr></table></div></figure>
<p>上述程式碼就算最後執行了 discard() 自動更新沒有被取消,檢查實際將執行的 sql 開出 log 記錄如下</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>Hibernate: update purchase_sheet_det set ...
</span><span class='line'>Hibernate: select ... from batch this_ where this_.name=?</span></code></pre></td></tr></table></div></figure>
<p>可以看到,在執行查詢 batch 之前就先對 update purchase_sheet_det 進行更新,當然之後的 <code>purchaseSheetDet.discard()</code> 就會失效,因為 update 語法已經在之前就已執行了。</p>
<p>筆者推測當你下 find 時 gorm 為了確保查到的資料是正確的,會強制將未 persist 的 domain 進行 save。</p>
<p>知道發生的原因後,我們可以將程式改寫如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>def batch = Batch.findByName(params["batch.name"])
</span><span class='line'>
</span><span class='line'>def purchaseSheetDet = PurchaseSheetDet.get(params.id)
</span><span class='line'>purchaseSheetDet.properties = params
</span><span class='line'>purchaseSheetDet.batch = batch
</span><span class='line'>
</span><span class='line'>purchaseSheetDet.discard()</span></code></pre></td></tr></table></div></figure>
<p>一旦我們這樣調整順序以後,在預計要更新的 domain 之前,先把要 find 的 domain 準備好,這樣就不會因為 find 去觸動自動將有變動的 domain 進行更新,造成 <code>discard()</code> 指令失效。</p>
<h2>結論</h2>
<p>透過上面的結果,我們可以整理出幾個規則:</p>
<ol>
<li>若操作的 domain 的屬性變更有可能因為之後的檢查透過 discard 放棄變更,在檢查的過程中務必不可觸動 find 的執行,避免因為 find 把尚未確定的變更寫入實體資料庫</li>
<li>若有必要使用到其他 domain 透過 find 取得實體,務必在可能執行 discard 的 domain 實體話之前透過 find 取得</li>
<li>一旦上述規則都有遵守,但執行 discard 還是無效,可以透過開啟 hibernate 的 sql log 來檢查到底何時執行了更新的語法。開啟 sql log 的方式可參考:<a href="http://stackoverflow.com/questions/2568507/how-to-log-sql-statements-in-grails">How to log sql statements in grails</a></li>
</ol>
<p><a rel="bookmark" href="http://smlsun.com/blog/2014/03/31/grails-gorm-discard/"></a></p>]]></content>
</entry>
<entry>
<title type="html"><![CDATA[AWS S3: 設定特定網站直接存取雲端圖片(不使用 accessKey ,secretKey)]]></title>
<link href="http://smlsun.com/blog/2014/03/31/AWS-S3-noaccessKey-nosecretKey/"/>
<updated>2014-03-31T00:00:00+08:00</updated>
<id>http://smlsun.com/blog/2014/03/31/AWS-S3-noaccessKey-nosecretKey</id>
<content type="html"><![CDATA[<p>關於 Amazon S3 的申請,可參考下列文章:</p>
<ul>
<li><a href="http://s3131212.com/amazon-simple-storage-service/">Amazon Simple Storage Service (Amazon S3) </a>: 介紹如何註冊使用 S3 服務</li>
</ul>
<p>一旦申請完成,我們可以透過 Make Public 來使資源可以透過 url 直接存取,一旦設定完成可以使用下列範例網址直接存取而不需要 accessKey 以及 secretKey:</p>
<p><code>https://s3.amazonaws.com/upload.sample.net/attachment/XXXXXX/111111.jpg</code></p>
<p>這樣做有什麼好處?既然使用 S3 作為圖片或是檔案的來源,當然希望可以利用 S3 的服務分散網站運作所需資源,特別是圖檔呈現部分,其中包括圖檔 cache 的機制,也將由 S3 進行判斷。</p>
<p>不過若將 S3 存放的檔案設為 public 看來是最快的方式,卻令資源暴露在網際網路之中,有沒有更折衷的方式,只允許目標網站可以以不透過 accessKey 以及 secretKey 進行存取,單純透過 url?答案是可以的,首先點選 <code>Edit bucket polocy</code>,如下圖:</p>
<p><img src="https://lh6.googleusercontent.com/-upmGYOEmyR8/UzkIupOGWtI/AAAAAAAAOSg/-bn11ssJVsA/s0/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-03-31+%E4%B8%8B%E5%8D%882.12.49.png" title="螢幕快照 2014-03-31 下午2.12.49.png" alt="enter image description here" /></p>
<p>點選之後會跳出定義 bucket polocy 視窗,我們可以填入下列 json 格式:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>{
</span><span class='line'> "Version": "2012-10-17",
</span><span class='line'> "Id": "S3PolicyId1",
</span><span class='line'> "Statement": [
</span><span class='line'> {
</span><span class='line'> "Sid": "IPAllow",
</span><span class='line'> "Effect": "Allow",
</span><span class='line'> "Principal": {
</span><span class='line'> "AWS": "*"
</span><span class='line'> },
</span><span class='line'> "Action": "s3:*",
</span><span class='line'> "Resource": "arn:aws:s3:::upload.net/*",
</span><span class='line'> "Condition": {
</span><span class='line'> "IpAddress": {
</span><span class='line'> "aws:SourceIp": "192.168.0. 1/24"
</span><span class='line'> }
</span><span class='line'> }
</span><span class='line'> },
</span><span class='line'> {
</span><span class='line'> "Sid": "Allow get requests originated from sample.net",
</span><span class='line'> "Effect": "Allow",
</span><span class='line'> "Principal": "*",
</span><span class='line'> "Action": "s3:GetObject",
</span><span class='line'> "Resource": "arn:aws:s3:::upload.sample.net/*",
</span><span class='line'> "Condition": {
</span><span class='line'> "StringLike": {
</span><span class='line'> "aws:Referer": [
</span><span class='line'> "http://sample.net/*",
</span><span class='line'> "http://dev.sample.net:8080/*",
</span><span class='line'> "http://www.sample.net/*"
</span><span class='line'> ]
</span><span class='line'> }
</span><span class='line'> }
</span><span class='line'> }
</span><span class='line'> ]
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>上述設定檔中,定義 <code>aws:SourceIp</code> 可以讓我們在主機上操作 S3 而不需要 accessKey 以及 secretKey,舉例來說可以透過如同 <code>wget https://s3.amazonaws.com/upload.sample.net/attachment/XXXXXX/111111.jpg</code> 來取得資源。</p>
<p>定義 <code>aws:Referer</code> 的話,則可以指定特定的 domain 才可以進行資源的存取,也就是說一旦使用者瀏覽 <code>www.sample.net</code> 這個網站時,則該瀏覽器在讀取圖檔時傳送目前所屬網址資訊(refer 屬性),則圖檔就可以直接透過 url 向 S3 取得檔案資源。</p>
<p>當然這些 polocy 變化百百種我們可以參考 aws 提供的範例進行修改:<a href="https://docs.aws.amazon.com/AmazonS3/latest/dev/AccessPolicyLanguage_UseCases_s3_a.html">Example Cases for Amazon S3 Bucket Policies</a></p>
<h1>結論</h1>
<p>透過上述的設定,可以帶來的好處:</p>
<ol>
<li>S3 分散了資源載入所需系統效能,還有頻寬</li>
<li>判斷是否重新取得(http starus 200) 或是沒有變動使用快取(http starus 304) 交由 S3 判斷,自行開發的服務不需實作。</li>
<li>安全性與方便度兼具,可以直接透過 url 存取,又不致於暴露在網際網路之中,透過定義 polocy 指定特定的 domain 可以直接存取支援</li>
</ol>
<p>經由這次對 S3 操作上的了解,若想要製作網站靜態資源的快取機制,也就不是難事,下一篇在說明如何自行開發快取機制。</p>
<p><a rel="bookmark" href="http://smlsun.com/blog/2014/03/31/AWS-S3-noaccessKey-nosecretKey/"></a></p>]]></content>
</entry>
<entry>
<title type="html"><![CDATA[服務上線發表-Moto Ranger 線上摩托維修記錄]]></title>
<link href="http://smlsun.com/blog/2014/01/20/Moto-Ranger/"/>
<updated>2014-01-20T00:00:00+08:00</updated>
<id>http://smlsun.com/blog/2014/01/20/Moto-Ranger</id>
<content type="html"><![CDATA[<p>服務上線發表,不知道別人在產品上線的情形是如何,我個人是非常緊張…</p>
<p>因為家裡開的是機車維修店,常常遇到客戶明明很久以前就換過機油,或是輪胎煞車皮之類的日常保養,卻還是說我前不久才換過,苦無證據,因此開發了一個可以記錄客戶維修歷史記錄的服務,順便也幫傳統的機車行加入一些資訊管理。</p>
<p>傳統摩托維修店家,沒有一個簡易的維修記錄軟體,很難記錄到底熟客有哪些,一個月的營業額有多少,所使用的零件成本也是憑感覺,往往沒辦法確實估算一個車行營運的成本與利潤,並且沒有辦法好好管理客戶資料,有時候確實會發生車子修理好,但是聯絡客戶的資訊找不到的窘境,維修車行是個需要專業並且辛苦的工作,工時又長,來客就是賺錢的機會,憑著維修記錄,也可以提醒客戶該做定期的維修保養,提供完善的服務。</p>
<p>並且在車行的營業環境中會有很多粉塵,在開發此系統時,也考慮提供手機或是平板瀏覽器(android 或是 iOS)的相容,即使沒有 PC 也可以進行。</p>
<p>在打造維修記錄的服務時,一開始的開發對象是店家的維修記錄的管理,但在過程中,發現個人也有需求,相信有在開車或是騎車的朋友一定也會有忘記自己的愛車是什麼時候加過機油(在下也是),觀察一下網路上的服務,甚至是手機 app 似乎沒有一個比較合適簡單的維修記錄軟裡,也有看到很多朋友使用 blog 來記錄維修記錄(辛苦!),每個人的生命中多少有幾個 [第二老婆] 陪你浪跡天涯,好好愛護才能走更遠的路,萬一因為忘記什麼時候加過機油造成引擎損壞,那就得不償失了。</p>
<p>此外,一旦您的愛車要出售時,也可以附上維修記錄,證明皆有正常維修,讓你的愛車更有價格上的競爭力。</p>
<p>於是基於店家的管理,以及個人的需求,筆者開發一個線上服務,恭敬的為大家介紹;</p>
<h2><a href="http://motoranger.net/">Moto Ranger - 線上摩托維修記錄</a></h2>
<p><img src="https://lh6.googleusercontent.com/--p7y5sSzaIw/UtzT00Q2siI/AAAAAAAAMEs/n3MRFddM8jE/s0/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-01-20+%E4%B8%8B%E5%8D%883.43.42.png" title="螢幕快照 2014-01-20 下午3.43.42.png" alt="enter image description here" /></p>
<h2>店家首頁</h2>
<p><img src="https://lh5.googleusercontent.com/-Z0OB8dowktA/Utzjhr4-Q3I/AAAAAAAAMFU/_LNNBRVDtYU/s0/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-01-20+%E4%B8%8B%E5%8D%883.52.24.png" title="螢幕快照 2014-01-20 下午3.52.24.png" alt="enter image description here" /></p>
<h2>維修記錄</h2>
<p><img src="https://lh4.googleusercontent.com/-7x73juU0x9Q/Ut0Cq2N_ezI/AAAAAAAAMF8/w2ZkLskZJe8/s0/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-01-20+%E4%B8%8B%E5%8D%887.03.22.png" title="螢幕快照 2014-01-20 下午7.03.22.png" alt="enter image description here" /></p>
<p>取名為 Moto Ranger 是要向我「<a href="http://blog.smlsun.com/2013/12/this-guy-is-my-bro.html">那流浪漢黑手 bro</a>」,「<a href="http://blog.tin613.com/">tin613</a>」 致敬,勇於追逐自己的夢想,雖然這只是一個小服務,但這是我的第一步,2013 年的總結,雖不知道這服務會發展到什麼程度,但至少在路上了,歡迎大家來使用。</p>
<p>再將近一年陸陸續續的開發中,由家裡的師父當我的早期試用者,陸陸續續也改善了一些流程的問題,在 2013 年末 密集的開發下,總算可以推出供個人與車行使用的線上維護記錄軟體,為了因應個資法,也加入相關的資料防護,方便大家使用的通用維修項目,以及使用導覽,若一開始不知道如何操作的朋友,註冊完成後,登入系統會先進行導覽,幫助您快速上手。</p>
<p>若是個人使用,可以直接線上註冊,若是車行有興趣,可以與我聯繫,期初我會在挑選幾家進行早期測試。</p>
<p>因為剛開始公開測試,若再使用中有任何問題或意見歡迎來信建議,或是直接在服務中的「意見回饋」進行留言,您的意見會讓這個服務更好!</p>
<p>或者,您也可以在我們的 <a href="https://www.facebook.com/pages/%E5%8B%9D%E7%A5%A5%E6%A9%9F%E8%BB%8A%E8%A1%8C-Moto-Ranger/252043181598683">facebook 專頁 Moto Ranger</a> 留下您的意見。</p>
<p><a rel="bookmark" href="http://smlsun.com/blog/2014/01/20/Moto-Ranger/"></a></p>]]></content>
</entry>
<entry>
<title type="html"><![CDATA[ubuntu 忘記密碼怎麼辦?三個步驟完成修改]]></title>
<link href="http://smlsun.com/blog/2014/01/19/ubuntu/"/>
<updated>2014-01-19T00:00:00+08:00</updated>
<id>http://smlsun.com/blog/2014/01/19/ubuntu</id>
<content type="html"><![CDATA[<ol>
<li><p>游標移到 recovery mode (不要 enter),鍵盤輸入 e,表示編輯 command</p>
<p> <img src="https://lh3.googleusercontent.com/-IIwTTUyywEk/UsYdxqpauxI/AAAAAAAAL_k/drrfABMruQI/s0/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-01-03+%E4%B8%8A%E5%8D%8810.16.36.png" title="螢幕快照 2014-01-03 上午10.16.36.png" alt="enter image description here" /></p></li>
<li><p>修改關鍵字 <code>ro recovery nomodeset</code>:</p>
<p> <img src="https://lh5.googleusercontent.com/-WMKHLt4bS5s/UsYgsBGuviI/AAAAAAAAL_4/q8PIplU5CLU/s0/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-01-03+%E4%B8%8A%E5%8D%8810.29.47.png" title="螢幕快照 2014-01-03 上午10.29.47.png" alt="enter image description here" /></p>
<p> ro 表示 readonly,標準只有提供幾個內建功能,若要修改密碼我們需要用到 bash 的 passwd 的指令,將其改為 <code>rw single init=/bin/bash</code>,表示進入 single user mode,也就是 root 使用者,並且載入 bash 令我們可以修改密碼:</p>
<p> <img src="https://lh5.googleusercontent.com/-V7A7bn00g50/UsYhmVEx6gI/AAAAAAAAMAE/v8B8EEJwl1k/s0/%E8%9E%A2%E5%B9%95%E5%BF%AB%E7%85%A7+2014-01-03+%E4%B8%8A%E5%8D%8810.33.42.png" title="螢幕快照 2014-01-03 上午10.33.42.png" alt="enter image description here" /></p></li>
<li><p>修改完成後,鍵盤輸入 ctrl-x 進入 recovary:</p>
<p> 可以看到游標停在 <code>root@(none):/#:</code>,現在我們有了 root 權限,如此一來在有最大權限的情況下,你就可以任意調整 server</p></li>
<li><p>輸入 <code>passwd username</code> 就可以進行修改密碼。</p></li>
</ol>
<h3>完工</h3>
<p><a rel="bookmark" href="http://smlsun.com/blog/2014/01/19/ubuntu/"></a></p>]]></content>
</entry>
<entry>
<title type="html"><![CDATA[Rabbit Mq Spring Boot]]></title>
<link href="http://smlsun.com/blog/2014/01/19/rabbit-MQ-Spring-boot/"/>
<updated>2014-01-19T00:00:00+08:00</updated>
<id>http://smlsun.com/blog/2014/01/19/rabbit-MQ-Spring-boot</id>
<content type="html"><![CDATA[<hr />
<p>title: “rabbit MQ 與 Spring boot 整合,以 RPC mode 為例”
layout: post</p>
<h2>categories: rabbit MQ, java, spring, spring boot</h2>
<p>最近在開發的專案剛好有使用到 rabbit MQ,也花了一點時間了解運作原理,關於使用 MQ 的好處,與各種應用情形,網路上有很多,有需要可以搜尋看看,本篇要說明的是 rabbit MQ 與 java 的串接,在實作的過程中花了一些時間,希望透過這篇的說明可以幫助到有需要的人。</p>
<p>在開始說明前,先簡單說明一下 RPC 模式的運作:</p>
<p>在 rabbit MQ 的官網<a href="http://www.rabbitmq.com/tutorials/tutorial-six-python.html">關於 RPC 的介紹</a>,模型示意圖如下:</p>
<p><img src="http://www.rabbitmq.com/img/tutorials/python-six.png" alt="enter image description here" /></p>
<p>可以看到在 RPC 模式下分為 client 與 server,request 與 Reply 分別有各自的隊列負責,本範例使用 spring AmqpTemplate 作為 client 進行操作,在上圖中總共有四個重要的成員,分為 client 與 server 跟大家介紹。</p>
<p>一開始要先跟大家說明的是,在整個 java 與 rabbit MQ 整合有個很重要的物件為 <code>ConnectionFactory connectionFactory</code>,該物件存放了存取 rabbit MQ server 的相關資訊,包括登入帳號與密碼,因此在此範例中不管 client、server 或是去跟回的隊列都會跟他有關係,因此一開始我們需要先建立 ConnectionFactory。</p>
<p>因為是用 spring boot 開發,預設只要有使用到 amqp 套件,spring 在啟動時就算你沒有特別設定,就會產生好有預設參數的 bean 等待你取用,本範例沒有用到特殊參數,因此透過下列程式就可以取得 ConnectionFactory 實體:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@Autowired
</span><span class='line'>ConnectionFactory connectionFactory;</span></code></pre></td></tr></table></div></figure>
<p>所謂的預設值分別是 ip、port、帳號、密碼等,client 與 server 用的會是一個物件,接著我們就可以來看 client 的定義。</p>
<h2>client</h2>
<p>client 是訊息的發起方,一開始我們需要先定義 client 的隊列,RPC 模式有去有回,對於 client 而言,他需要消化的隊列是 reply,因此我們需要先將 client 與 reply 進行綁定,首先需要先定義 reply 隊列:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>final static String queueName = "reply";
</span><span class='line'>
</span><span class='line'>@Bean
</span><span class='line'>public Queue responseQueue() {
</span><span class='line'> return new Queue(queueName);
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>一旦隊列建立完成,再來要設定 amqpTemplete,還記得剛剛說的 connectionFactory 儲存了存取 MQ server 的相關資訊,因此在建立時需要傳入,建立以後綁定 reply 隊列,如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@Bean
</span><span class='line'>public RabbitTemplate amqpTemplate() {
</span><span class='line'> RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
</span><span class='line'> rabbitTemplate.setReplyQueue(responseQueue());
</span><span class='line'> return rabbitTemplate;
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>接著我們需要設定 SimpleMessageListenerContainer,首先為了讓 SimpleMessageListenerContainer 可以存取隊列訊息,所以他也需要 ConnectionFactory,一旦 amqpTemplate 送出訊息後,將透過 SimpleMessageListenerContainer 去監看 replay 的隊列有沒有新的訊息近來並且要將訊息傳給 amqpTemplate,因此 SimpleMessageListenerContainer 需要綁定 amqpTemplate 與 replay 隊列,設定的程式碼如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@Bean
</span><span class='line'>public SimpleMessageListenerContainer clientMessageListenerContainer() {
</span><span class='line'> SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
</span><span class='line'> container.setConnectionFactory(connectionFactory);
</span><span class='line'> container.setQueues(responseQueue());
</span><span class='line'> container.setMessageListener(amqpTemplate());
</span><span class='line'> return container;
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>如此就完成 client 的設定,完整程式碼如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>
<span class='line-number'>33</span>
<span class='line-number'>34</span>
<span class='line-number'>35</span>
<span class='line-number'>36</span>
<span class='line-number'>37</span>
<span class='line-number'>38</span>
<span class='line-number'>39</span>
<span class='line-number'>40</span>
<span class='line-number'>41</span>
<span class='line-number'>42</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>package goinfo.test;
</span><span class='line'>
</span><span class='line'>import org.springframework.amqp.core.Queue;
</span><span class='line'>import org.springframework.amqp.rabbit.connection.ConnectionFactory;
</span><span class='line'>import org.springframework.amqp.rabbit.core.RabbitTemplate;
</span><span class='line'>import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer;
</span><span class='line'>import org.springframework.beans.factory.annotation.Autowired;
</span><span class='line'>import org.springframework.context.annotation.Bean;
</span><span class='line'>import org.springframework.context.annotation.Configuration;
</span><span class='line'>
</span><span class='line'>@Configuration
</span><span class='line'>public class MqClientConfig {
</span><span class='line'>
</span><span class='line'>
</span><span class='line'> @Autowired
</span><span class='line'> ConnectionFactory connectionFactory;
</span><span class='line'>
</span><span class='line'> final static String queueName = "reply";
</span><span class='line'>
</span><span class='line'> @Bean
</span><span class='line'> public Queue responseQueue() {
</span><span class='line'> return new Queue(queueName);
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'> @Bean
</span><span class='line'> public RabbitTemplate amqpTemplate() {
</span><span class='line'> RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
</span><span class='line'> rabbitTemplate.setReplyQueue(responseQueue());
</span><span class='line'> return rabbitTemplate;
</span><span class='line'> }
</span><span class='line'> @Bean
</span><span class='line'> public SimpleMessageListenerContainer clientMessageListenerContainer() {
</span><span class='line'> SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
</span><span class='line'> container.setConnectionFactory(connectionFactory);
</span><span class='line'> container.setQueues(responseQueue());
</span><span class='line'> container.setMessageListener(amqpTemplate());
</span><span class='line'>
</span><span class='line'> return container;
</span><span class='line'> }
</span><span class='line'>
</span><span class='line'>
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<h1>server</h1>
<p>server 的部分同樣需要 ConnectionFactory connectionFactory,跟 client 一樣,這邊不多說明,除了 connectionFactory,第一步要定義的是 server 所要消化的對列,也就是上圖中的 rpc_queue,我們將隊列命名為 spring-boot 如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@Autowired
</span><span class='line'>ConnectionFactory connectionFactory;
</span><span class='line'>
</span><span class='line'>final static String queueName = "spring-boot";
</span><span class='line'>
</span><span class='line'>@Bean
</span><span class='line'>Queue queue() {
</span><span class='line'> return new Queue(queueName, false);
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>我們需要一個類別來處理由 rpc_queue 傳入的訊息,並且定義一旦接收到 message 之後,要呼叫哪個函式,透過 MessageListenerAdapter 來幫我們完成:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@Bean
</span><span class='line'>AmqpController receiver() {
</span><span class='line'> return new AmqpController();
</span><span class='line'>}
</span><span class='line'>@Bean
</span><span class='line'>MessageListenerAdapter listenerAdapter(AmqpController receiver) {
</span><span class='line'> return new MessageListenerAdapter(receiver, "receiveMessage");
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>AmqpController 是我們要處理 message 的類別,而 <code>MessageListenerAdapter(receiver, "receiveMessage");</code>這一句表示,一旦接收到訊息,要呼叫 receiver 物件中所定義的 <code>receiveMessage</code> 方法, AmqpController 程式碼如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>
</span><span class='line'>import goinfo.service.ApiFecadeService;
</span><span class='line'>import org.springframework.beans.factory.annotation.Autowired;
</span><span class='line'>
</span><span class='line'>public class AmqpController {
</span><span class='line'>
</span><span class='line'> @Autowired
</span><span class='line'> private ApiFecadeService apiFecadeService;
</span><span class='line'>
</span><span class='line'> public String receiveMessage(String message) {
</span><span class='line'> return apiFecadeService.excute(apiFecadeService, message);
</span><span class='line'> }
</span><span class='line'>}
</span></code></pre></td></tr></table></div></figure>
<p>可以看到我們定義了 receiveMessage method,所傳入的 message 將透過 MessageListenerAdapter 傳入,return 的部份就是要傳入 reply 隊列的內容。</p>
<p>這邊不得不補充一下,自己在研究時,一直想不透到底要怎麼將處理的結果回傳給 reply,因為 spring AMQP 官方的範例是最單純的有去沒回的模式,因此在宣告 receiveMessage 是 void 而不是 String,嘗試許多方法,靈機想說該不會直接 return 就會傳給 reply,果不其然…就是這麼簡單!</p>
<p>一旦 MessageListenerAdapter 設定好,我們同樣需要設定 SimpleMessageListenerContainer,該物件的建立同樣需要 ConnectionFactory 作為存取隊列的依據,與 client 類似,但不一樣的是隊列對象是 rpc_queue,而訊息的處理交由 MessageListenerAdapter 傳入 AmqpController,設定如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
</pre></td><td class='code'><pre><code class=''><span class='line'>@Bean
</span><span class='line'>SimpleMessageListenerContainer serverMessageListenerContainer(MessageListenerAdapter listenerAdapter) {
</span><span class='line'> SimpleMessageListenerContainer container = new SimpleMessageListenerContainer();
</span><span class='line'> container.setConnectionFactory(connectionFactory);
</span><span class='line'> container.setQueues(queue());
</span><span class='line'> container.setMessageListener(listenerAdapter);
</span><span class='line'> return container;
</span><span class='line'>}</span></code></pre></td></tr></table></div></figure>
<p>完整的程式如下:</p>
<figure class='code'><div class="highlight"><table><tr><td class="gutter"><pre class="line-numbers"><span class='line-number'>1</span>
<span class='line-number'>2</span>
<span class='line-number'>3</span>
<span class='line-number'>4</span>
<span class='line-number'>5</span>
<span class='line-number'>6</span>
<span class='line-number'>7</span>
<span class='line-number'>8</span>
<span class='line-number'>9</span>
<span class='line-number'>10</span>
<span class='line-number'>11</span>
<span class='line-number'>12</span>
<span class='line-number'>13</span>
<span class='line-number'>14</span>
<span class='line-number'>15</span>
<span class='line-number'>16</span>
<span class='line-number'>17</span>
<span class='line-number'>18</span>
<span class='line-number'>19</span>
<span class='line-number'>20</span>
<span class='line-number'>21</span>
<span class='line-number'>22</span>
<span class='line-number'>23</span>
<span class='line-number'>24</span>
<span class='line-number'>25</span>
<span class='line-number'>26</span>
<span class='line-number'>27</span>
<span class='line-number'>28</span>
<span class='line-number'>29</span>
<span class='line-number'>30</span>
<span class='line-number'>31</span>
<span class='line-number'>32</span>