Spring Integration in Action:File

? EIP的兩個(gè)靠譜框架:si和mule3,在基本面上思路一致:基于發(fā)揚(yáng)光大Spring OO的解耦能力,達(dá)到配置式使用、一定要用上spring xml schema;基于配置組裝程序、基本的使用盡量走配置不走代碼,這樣si基本組件的重用性高。當(dāng)然mule作為商業(yè)產(chǎn)品更進(jìn)一步,基于Spring實(shí)現(xiàn)了對(duì)象熱加載熱替換。

類似html以及soap的Header + Body;spring的消息是Header + PayLoad;
html是:head + body?? 大道至簡(jiǎn)、人心所向;

? spring-core有對(duì)消息的抽象,消息。不管你什么協(xié)議就倆個(gè)部分:Header和PayLoad:

文件、消息、RPC、共享數(shù)據(jù)庫,除了數(shù)據(jù)庫其他一律封裝為Header + PayLoad.

? Spring Integration provides abstractions over messaging and also other transports such as HTTP, TCP, and others.? si進(jìn)一步屏蔽掉不同協(xié)議,不管你是什么協(xié)議來到的消息,一視同仁全是org.springframework.messaging.Message.? si從spring一路繼承下來:

Message.

?? |

GenericMessage 常用

?? |

AdviceMessage

? 第二個(gè)si會(huì)用到的是:org.springframework.messaging.support.ErrorMessage,當(dāng)發(fā)生錯(cuò)誤異常時(shí),si的異常處理器會(huì)創(chuàng)建該類型消息、將原始出錯(cuò)消息包裝其中,比如我們?cè)谵D(zhuǎn)發(fā)http消息時(shí)可能對(duì)方服務(wù)下線了:

<int-http:outbound-channel-adapter url="http://..." channel="httpOut">

? <int-http:request-handler-advice-chain> //為了捕獲錯(cuò)誤并處理可以配置advice-chain

??? <int:retry-advice recovery-channel="recoveryTransformChannel"/> //出錯(cuò)時(shí)ErrorMessage的去處

? ? </int-http:request-handler-advice-chain>

</int-http:outbound-channel-adapter>

? 我們希望拿到錯(cuò)誤消息以及出錯(cuò)原始消息、存儲(chǔ)成文件、再做后續(xù)處理(這是一個(gè)比較通用的處理方式,因?yàn)槲募到y(tǒng)是最基本的)。可以使用轉(zhuǎn)換器:

<int:transformer requires-reply="false" method="transformErrorMessage" input-channel="recoveryTransformChannel" output-channel="recoveryChannel" ref=......

? 在這個(gè)轉(zhuǎn)換器(POJO)的處理方法當(dāng)中,收到的消息類型就是ErrorMessage,原始消息也在里面,想怎么處理隨意。這種異常/錯(cuò)誤處理思路,頗契合函數(shù)式的方法調(diào)用鏈,在一連串的方法調(diào)用鏈路上,中間出了異常也沒關(guān)系,將異常當(dāng)做新的消息繼續(xù)往下傳遞。

? 從文件處理開始了解si,同時(shí)參考官方文檔。一些文件IO的最佳實(shí)踐。最佳實(shí)踐建議不要自己用字符串拼文件路徑文件名,而要盡量使用java.io.File處理,這樣才能充分利用JVM的平臺(tái)隔離能力保證你的代碼跨平臺(tái)。再就還是要用buffer包裝stream適配磁盤:IO performance depends a lot from the buffering strategy. Usually, it's quite fast to read packets with the size of 512 or 1024 bytes because these sizes match well with the packet sizes used on harddisks in file systems or file system caches. But as soon as you have to read only a few bytes and that many times performance drops significantly.

? 文件形式消息的處理包括:從文件系統(tǒng)pick文件、掃描過濾目錄、寫文件、處理并發(fā)讀寫。EIP這本經(jīng)典定義的四種信息交互模式文件傳輸、共享數(shù)據(jù)庫、RPC和消息,前兩種都是將信息通過第三方中介傳輸(JMS也是)需要更多序列化反序列化,后兩種屬于兩個(gè)交互系統(tǒng)間內(nèi)存到內(nèi)存,理論上性能高一點(diǎn),但沒有了第三方可靠保障。早期同一臺(tái)機(jī)器上的兩個(gè)進(jìn)程可以采用磁盤互相交換數(shù)據(jù)信息,交互系統(tǒng)如果都在一臺(tái)機(jī)器,可以直接讀寫磁盤,不在一臺(tái),可以借助FTP.

? JMS和郵件都是有第三方服務(wù)器的消息形式,對(duì)于文件交互來說,磁盤也相當(dāng)于第三方服務(wù)。不要小看文件傳輸方式,相比消息或webService似乎它太老了,但是文件傳輸比它們都要簡(jiǎn)單可靠、更通用,并不過時(shí)。很多企業(yè)費(fèi)勁巴拉趕時(shí)髦上SOA特別是soap WS,結(jié)果發(fā)現(xiàn)被坑了,SOA沒有錯(cuò),是復(fù)雜而無用的soap坑爹,這些企業(yè)最終還是保留了很多文件傳輸方式。文件寫入磁盤,這本身就是容錯(cuò),此為優(yōu)于soap WS的第一點(diǎn),第二點(diǎn):soap充斥著沒什么實(shí)際用處、過度復(fù)雜的規(guī)范,而磁盤和文件系統(tǒng)成熟、簡(jiǎn)單,文件仍然是simplest thing that might possibly work. If a simple solution just works, it has earned a right to stay in business.

Can you be friends with the filesystem?

? 大多javaer像怕并發(fā)一樣怕和文件系統(tǒng)打交道,這沒必要,比如在UNIX中,一切皆文件,編寫OS的人想要做點(diǎn)信息交互可以依賴什么?只有文件系統(tǒng),文件系統(tǒng)的優(yōu)點(diǎn)對(duì)缺點(diǎn):

1、容錯(cuò),容許應(yīng)用crash? -? 額外的復(fù)雜:資源、鎖;

2、磁盤空間大? -? 磁盤慢;

3、文件處理比較簡(jiǎn)單? -? 文件系統(tǒng)無一致性保障,像什么隔離、ACID或rest語義都沒有,自己實(shí)現(xiàn)也不是障礙;

? 文件作為一個(gè)功能模塊,SI專門提供有file命名空間,像這樣:

<beans xmlns="http://www.springframework.org/schema/beans"

xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"

xmlns:file="http://www.springframework.org/schema/integration/file"

xsi:schemaLocation=".....

http://www.springframework.org/schema/integration/file

http://www.springframework.org/schema/integration/file/

?spring-integration-file.xsd">

<file:inbound-channel-adapter channel="incomingChanges" directory="#{config.diary.store}"/>

? file:inbound-channel-adapter屬于一種單向的接收文件的EndPoint端點(diǎn)。SI中可以理解端點(diǎn)是與外部交互的、Channel管道則是在內(nèi)部流轉(zhuǎn)消息的。

? schema是spring達(dá)到重用目標(biāo)的重要基石,使用file命名空間前綴,在eclipse和idea都可以快速定義組件包括channel adapter管道、各種transformer轉(zhuǎn)換器(支持把文件內(nèi)容讀取為strings or byte arrays.),雖然它們?nèi)靠梢匀匀皇褂迷嫉腷ean標(biāo)簽定義,比如上述file:inbound-channel-adapter也可以按照bean配置:

<bean id="pollableFileSource" class="org.springframework.integration.file.FileReadingMessageSource" p:directory="${input.directory}"/>

? 但這樣味道就不對(duì)了,完全按照shema定義出來的配置,近乎對(duì)程序整體的一個(gè)自然語言描述,至于代碼,都是si封裝好的那一套,這就是對(duì)si代碼的高效重用,OO講究隔離變化,變化是怪獸、把變化從代碼中剝離、用配置封印。spring配置本質(zhì)上就是做組裝的、做程序裝配的。

11.5 Under the hood

? 進(jìn)入filesystem integration的瑣碎細(xì)節(jié),有助于你理解spring-integration-file項(xiàng)目,不過只是使用的話不看也可以。首先討論讀取文件時(shí)的排序問題,這倆問題本來是寫文件引發(fā)的,但是體現(xiàn)在讀文件時(shí)。讀寫之間互斥的是必須要有的,且這個(gè)問題與OS相關(guān),讀寫之間沒有互斥鎖,上一點(diǎn)點(diǎn)量、讀很容易就會(huì)讀到unfinished files,作為不完整的非法消息進(jìn)入系統(tǒng),后面異常等著你。

11.5.1 FRMS 讀文件

? 只用JDK讀文件很套路了,用Buffer包裝Stream:

BufferedReader input = new BufferedReader(new FileReader(aFile));

StringBuilder contents = new StringBuilder(); //單線程構(gòu)造字符串用SB

try {

? String line = null;

? while (( line = input.readLine()) != null){

??? contents.append(line);

??? contents.append(System.getProperty("line.separator"));

? }

} finally {

? input.close();

}

? 用si就這么簡(jiǎn)單:

<file:inbound-channel-adapter directory="C:/....." auto-create-directory="false" />

文件入棧inbound管道channel

? <file:inbound-channel-adapter>配置元素會(huì)創(chuàng)建FileReadingMessageSource(寫是FileWritingMessageHandler),它是MessageSource的文件實(shí)現(xiàn),它是一個(gè)“被動(dòng)”passive組件,需要由外部定時(shí)調(diào)用其receive()方法“掃描”也就是poll指定目錄,由endpoint(channelAdapter或gateway)來做這個(gè)事。當(dāng)收取到文件消息時(shí),INFO日志示例:

o.s.i.file.FileReadingMessageSource : Created message: [GenericMessage [payload=C:\...\sql.txt, headers={file_originalFile=C:\...\recoveryChannel\sql.txt, id=88cd300e-8ae5-691e-8185-59a52b3c00be, file_name=sql.txt, file_relativePath=sql.txt, timestamp=1540960288297}]]

? FRMS作為消息源會(huì)如上創(chuàng)建GenericMessage<File>消息。

? 什么文件可以讀取處理,si管這叫做過濾,過濾掉什么樣的文件不能讀取處理,si提供一個(gè)擴(kuò)展點(diǎn):FileListFilter,你可以擴(kuò)展實(shí)現(xiàn)它以決定什么文件可以處理什么文件不處理,默認(rèn)自帶倆:IgnoreHiddenFileListFilter隱藏文件過濾(4.2引入)、AcceptOnceFileListFilter防重復(fù)讀取過濾。

? 我們知道文件鎖分為共享鎖(讀鎖)和排他鎖(寫鎖),共享和排他都是絕對(duì)的語義,簡(jiǎn)單說就是排他鎖和排他鎖共享鎖之間都是互斥的。si的實(shí)現(xiàn)比較mule還是簡(jiǎn)陋,si并沒有基于文件鎖做通用的文件讀寫互斥,如果寫入端和讀取端都是你的si程序那好說:

1、如果讀寫端都在一個(gè)context里,自動(dòng)互斥;

2、如果不是在一個(gè)context里但都是si項(xiàng)目:可以使用入棧管道的filename-pattern=“.msg”和出棧管道的temporary-file-suffix=“.tmp”基于后綴實(shí)現(xiàn)讀寫互斥;

3、如果有一端是不受控外部程序,比如寫入端是其他異構(gòu)系統(tǒng),那么還可以酌情使用LastModifiedFileListFilter:

<bean id="filter"class="org.springframework.integration.file.filters.LastModifiedFileListFilter">

? <propertyname="age" value="120" /> //只有存在超過120秒的文件才會(huì)被處理

</bean>


? 從5.0開始,以文件形式進(jìn)入的消息增加了如下Header供你使用:

1、FileHeaders.FILENAME常量:配置中?。篺ilename-generator-expression="headers.file_name"

2、FileHeaders.ORIGINAL_FILE:File本身,可用于拿到原始文件;headers.file_originalFile

3、FileHeaders.RELATIVE_PATH:用于帶目錄層次的需求;headers.file_relativePath

? 對(duì)應(yīng)一個(gè)文件消息的頭部是這樣:

headers={

file_originalFile=C:\...\34481a31-c7a6-652c-3e9f-86c4c7543261.msg,

id=5ec39fbf-3d78-1258-228e-a7ff68faf431,? //si的消息ID是UUID

file_name=34481a31-c7a6-652c-3e9f-86c4c7543261.msg,

file_relativePath=34481a31-c7a6-652c-3e9f-86c4c7543261.msg,

timestamp=1540796199647

}

文件排序

? 一些場(chǎng)景當(dāng)中,讀取文件的順序很重要(類比MQ的有序消息隊(duì)列),通用型FRMS必須能夠處理排序,F(xiàn)RMS列舉一個(gè)目錄下的文件使用了File’s listFiles()方法,它返回File[]、一個(gè)有序數(shù)組,但是這個(gè)順序在不同OS、不同配置下不一定一致,長(zhǎng)話短說,你必須提供一個(gè)Comparator來保障排序?,F(xiàn)在的問題是,當(dāng)文件不是按序?qū)懭氲臅r(shí)候,F(xiàn)RMS內(nèi)部隊(duì)列也必須能夠?qū)σ呀?jīng)包裝為消息的文件進(jìn)行重新排序:

FRMS內(nèi)部隊(duì)列

? 如果文件不是按照生成順序添加到內(nèi)部隊(duì)列的,F(xiàn)RMS是無所適從的,它自己不知道順序亂了。如果你寫文件的順序就是你希望它們被處理的順序、并且你提供了comparator排序器,那就好說了,F(xiàn)RMS會(huì)依據(jù)comparator讀取處理文件。不要妄想只依賴消息生成順序It’s never a good idea to depend on message order implicitly.? 如果不得不,你得定義resequencer重排器。


? 鎖這個(gè)東西,最大的麻煩是不同OS實(shí)現(xiàn)不一樣,幸好我們有JVM。si的轉(zhuǎn)換器將讀取文件當(dāng)前內(nèi)容(at the moment they reached the end of it.)并處理轉(zhuǎn)換為String or byte[],簡(jiǎn)單說就是有啥讀啥有多少讀多少,一個(gè)正在被某進(jìn)程寫入的文件,只要不被打開讀取、其引用隨便傳遞沒問題,而一旦還沒寫完就被打開讀取,就會(huì)讀到不完整內(nèi)容。避免這種premature早熟讀的最佳方式,是由寫進(jìn)程來決定該文件何時(shí)寫完了:你可以用寫完文件的轉(zhuǎn)儲(chǔ)移動(dòng)方式move-when-ready,給文件名加個(gè)前綴用來標(biāo)識(shí)還沒寫完的和已經(jīng)寫完的也屬于這種,舉例來說:如果讀寫兩端都是si,讀文件就可以和寫文件配合起來了,在寫文件一端,使用temporary-file-suffix來指定一個(gè)正在寫的文件(臨時(shí)文件)前綴(該屬性與append屬性互斥),文件寫完則修改文件名為正式文件名,正式文件名由一個(gè)文件名生成規(guī)則FileNameGenerator指定,讀寫兩端都需要遵守這個(gè)文件名生成規(guī)則,可將FileNameGenerator配置為獨(dú)立bean,在讀寫兩端的filename-generator屬性都指向這同一個(gè)bean.

? 其他判斷文件是否完成寫入的靠譜手段,包括比如對(duì)于xml格式的文件,可以依據(jù)xml根標(biāo)簽的結(jié)束作為判據(jù),其他的只要是有格式的內(nèi)容都可以采用類似方式。實(shí)際上這種方式也用于TCP,所以說TCP和文件有類似之處:都是字節(jié)流、既然是流、都需要有判斷結(jié)束的手段。TCP比文件更好的地方在于,TCP消息有頭,那么最簡(jiǎn)便的方式就是在頭部定義消息長(zhǎng)度,但是文件沒有,文件不分Header、Body。結(jié)束標(biāo)志這種方式顯然不如move-when-ready,它還得讀文件讀到末尾。再者對(duì)于一些并發(fā)寫的情況,可能末尾已經(jīng)寫完了但是中間部分還正在寫,比如BitTorrent就這么干的,再比如寫圖片也常這么干,其實(shí)這些都是有點(diǎn)low的tricky,正統(tǒng)搞法是文件鎖。

? 文件鎖美中不足的是:File locking works differently on different operating systems。JVM在java.nio庫對(duì)文件鎖有一個(gè)抽象,si在java.nio 2.0版本上同時(shí)支持文件的鎖和過濾,你可以注入NioFileLocker,當(dāng)然要求寫文件一端也具備對(duì)鎖的處理。鎖是比較完備的保底方案但是有一定復(fù)雜性。

? 在mule3中提供有使用 java.nio.channels.FileLock文件鎖 實(shí)現(xiàn)基本互斥的org.mule.transport.file.FileMessageReceiver,相當(dāng)于si的FileReadingMessageSource,但它是主動(dòng)poll的組件,它內(nèi)部有一個(gè)嘗試獲取文件鎖方法:

protected boolean attemptFileLock(File sourceFile) throws MuleException {

? FileLock lock =null;

? FileChannel channel =null;

? boolean fileCanBeLocked =false;

? try {

??? channel = (new RandomAccessFile(sourceFile,"rw")).getChannel();

??? lock = channel.tryLock();

??? //////////////////////////////////////【添加代碼】

? }catch (FileNotFoundException var24) {

??? throw new DefaultMuleException(FileMessages.fileDoesNotExist(sourceFile.getName()));

? }catch (IOException var25) {

? ;

? }finally {

??? if(lock !=null) {

??? fileCanBeLocked =true;

??? try {

??? lock.release();

??? }catch (IOException var23) {

??? ;

? }

}

if(channel !=null) {

try {

channel.close();

}catch (IOException var22) {

;

}

}

}

return fileCanBeLocked;

}

? 以上代碼用于讀取文件的場(chǎng)景下可用,但是如果寫文件的第三方進(jìn)程不講究,沒有按規(guī)矩加文件鎖的話就失效了,那么我們可以定義我們要讀的消息文件末尾比如在這個(gè)類中定義:

? private static String PublishEvent ="</PublishEvent>";

? 然后在上述【添加代碼】處增加判斷正確的文件內(nèi)容結(jié)尾:

byte[] dst =new byte[18];

long fileSize = channel.size();

int offset = (int)(fileSize -18L);

if(offset >=18 && fileSize >=512L) {

? int result = channel.read(ByteBuffer.wrap(dst), (long)offset);

? if(result != -1 && result !=0) {

????? String endTag =new String(dst,"UTF-8");

????? if(!endTag.contains(PublishEvent) ) {

????????? lock =null;

????? }

? }else {

? ? lock =null;

? }

}else {

? lock =null;

}

11.2 Writing files 寫文件

? 寫文件就比讀文件簡(jiǎn)單了,寫文件第一需要指定寫目錄,第二你得有一個(gè)字節(jié)數(shù)組或者是字符串去寫、以及一個(gè)文件名。如果只用JDK,代碼就是這么啰嗦:

Writer output = new BufferedWriter( new FileWriter(aFile) );?

try {

? output.write("This is written to the file in default encoding");

} finally { output.close(); }

? 用si的file:outbound-channel-adapter管道,替代了FileOutputStreams或FileWriters的功能:

<file:outbound-channel-adapter channel="receive" charset="UTF-8" directory="..."/>

adapter是一種單向的channel管道,如果需要對(duì)寫完的文件做postprocess后續(xù)處理可以用gateway

? 唯一需要你操心的只是組裝這些組件、適當(dāng)配置、兼顧讀取端:文件用什么編碼?、再一個(gè)就是讀寫互斥鎖的問題,當(dāng)你寫一個(gè)大文件,怎么阻止讀取端讀取未完成的文件?最簡(jiǎn)單傳統(tǒng)的方式就是文件寫完了才移動(dòng)到讀取端監(jiān)聽的目錄下或者重命名,file:outbound-channel-adapter也是首選改文件名:當(dāng)它寫文件時(shí),會(huì)先打開一個(gè)臨時(shí)文件、臨時(shí)文件有一個(gè)后綴名,供讀取端識(shí)別尚未寫完的文件,這樣寫入端就可以安全地寫入文件內(nèi)容、寫完之后對(duì)文件進(jìn)行改名、就o了。

? 正式的文件名字由FileNameGenerator提供,你可以將其作為一個(gè)bean聲明出來、再使用filename-generator屬性做注入,spring老司機(jī)明白的這里你就可以自定義了,默認(rèn)實(shí)現(xiàn)是:org.springframework.integration.file.DefaultFileNameGenerator,默認(rèn)行為是如果找不到合適文件名那么就用消息ID,消息ID即UUID也就是生成文件是:UUID.msg.

? file:outbound-channel-adapter可接受的消息payLoad類型包括byte[], File(文件搬運(yùn))以及String,該配置元素代表文件出棧管道,EIP術(shù)語里這也是一種endpoint端點(diǎn),端點(diǎn)包括輸入和輸出,概念涵蓋更大一些,只要是和外部系統(tǒng)做IO的地方都是端點(diǎn)。該元素屬性包括:

1、id: id和channel二選一必須有,The ID of the endpoint or the implicit channel leading to it

2、channel:把消息轉(zhuǎn)換到文件的管道,可以不配置,有默認(rèn)實(shí)現(xiàn);

3、directory:寫目錄,這個(gè)必須的;

4、filename-generator:文件名生成規(guī)則,默認(rèn)使用header或者是File對(duì)象的name,UUID;

5、delete-source-files:如果輸入消息也是文件,那么寫完會(huì)刪除這個(gè)源頭文件;

? 按照上述,一個(gè)最簡(jiǎn)文件出棧管道長(zhǎng)這樣:

<int:channel id="2GPMS" />

<file:outbound-channel-adapter id="2GPMS" temporary-file-suffix="-temp"

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? charset="UTF-8" directory="C:\....." auto-create-directory=""/>

? 有時(shí)候,你會(huì)遇到想要在代碼里直接獲取chennel Bean發(fā)送消息,那么可以首先實(shí)現(xiàn)ApplicationContextAware獲取到context,然后這么寫:

MessageChannel channel =context.getBean("2GPMS", MessageChannel.class);//拿到通道

Message message = MessageBuilder.withPayload(payload).build();//構(gòu)建消息,payload是文本格式的消息PayLoad.

channel.send(message, 10000);//fire

? 當(dāng)你遇到即需要把當(dāng)前消息往后傳遞、繼續(xù)處理,還需要馬上給發(fā)送者一個(gè)合適的ack響應(yīng)時(shí),上述就有用了。進(jìn)一步的,通過context可以做很多事:

File inDir = (File)new DirectFieldAccessor(context.getBean(FileReadingMessageSource.class)).getPropertyValue("directory");

LiteralExpression expression = (LiteralExpression)new DirectFieldAccessor(context.getBean(FileWritingMessageHandler.class)).getPropertyValue("destinationDirectoryExpression");

File outDir =new File(expression.getValue());

System.out.println("Input directory is: " + inDir.getAbsolutePath());

System.out.println("Output directory is: " + outDir.getAbsolutePath());

? 如上代碼,才是spring的正確使用姿勢(shì)。

? 在一些場(chǎng)景下你可能還得處理寫完的文件,此時(shí)可以使用<file:outbound-gateway/>,可以支持當(dāng)文件寫完以后立即發(fā)送給其他端點(diǎn),當(dāng)你需要在文件寫完之后通知其他服務(wù)時(shí),這是最佳方式。

? 既然是寫文件,有可能遭遇的異常是IOException,比如說寫磁盤出錯(cuò)了,si會(huì)拋出MessageDeliveryException(包含原始IOException),si允許你將此異常bubble up as a RuntimeException.? 還一種異常情況是可能無意覆蓋了已有文件,這個(gè)你就需要確保FileNameGenerator的文件名生成規(guī)則正確,規(guī)則就一條:不同的消息文件文件名要保證不同,DefaultFileNameGenerator可以保證在同一個(gè)Application Context下、文件名唯一。

? 文件這一章節(jié),舉的例子很像簡(jiǎn)書,是一個(gè)旅行日記Trip Diary、一個(gè)多客戶端協(xié)作編寫文本的例子,使用上述outbound adapter可以實(shí)現(xiàn)協(xié)作編輯器的增量保存功能,這要求文件可以正確識(shí)別以及有序,為此,文件名定義為客戶端應(yīng)用的key 進(jìn)程ID(which is unique for each time the application context is loaded)加時(shí)間戳,你可以把文件名規(guī)則自定義為一個(gè)Bean比如叫做ChangeFileNameGenerator,然后組裝它們:

<file:outbound-channel-adapter channel="outgoingChanges" directory="#{config.diary.store}" auto-create-directory="true" filename-generator="nameGenerator"/>

<bean id="nameGenerator" class="com.manning.siia.trip.diary.ChangeFileNameGenerator">

? <constructor-arg value="#{config.processId}"/>

</bean>

? 在具備Spring插件的idea環(huán)境下,社區(qū)版企業(yè)版都可以選中上述某個(gè)屬性點(diǎn)進(jìn)去查看xsdDoc. 上述文件出棧的圖形表示:

協(xié)作編輯器的寫文件管道

? Trip Diary module must read files from a directory to update the displayed diary with changes from other editors. 讀寫都有了,難點(diǎn)在于讀文件端決定哪個(gè)文件以及何時(shí)可以開始讀取,可以認(rèn)為讀文件端就像TCP監(jiān)聽一樣監(jiān)視一個(gè)目錄,所謂監(jiān)視即為周期性獲取到該目錄下的文件列表、判斷哪一個(gè)是新出現(xiàn)的可以讀取,看上去很簡(jiǎn)單實(shí)則不然,想象你正在監(jiān)視一個(gè)目錄,同時(shí)還有別人在往里拖放文件,一兩個(gè)文件好說,當(dāng)大批量文件同時(shí)刷新,哪個(gè)是哪個(gè)你就暈了。

? 默認(rèn)情況下,si的文件入棧讀取管道會(huì)對(duì)目錄下出現(xiàn)的每個(gè)文件只獲取一次,不會(huì)重復(fù)讀取,你也可以自定義擴(kuò)展FileListFilters.

? Java File API的核心是java.io.File,它是不可變的,意味著一旦創(chuàng)建再不會(huì)改變,這很好,特別是在并發(fā)處理當(dāng)中,這一點(diǎn)給你省了不少麻煩,以File對(duì)象作為PayLoad的消息是絕對(duì)線程安全的。

? 讀寫管道如果都位于一個(gè)applicationContext,那么他們之間可以自動(dòng)協(xié)作(也就是文件讀寫鎖都有了);如果不是比如你是讀端但是寫端是第三方系統(tǒng)(If the writing isn’t under your control, though),那你只能繼承AbstractFileListFilter 或?qū)崿F(xiàn) FileListFilter 去實(shí)現(xiàn)自定義的FileListFilter.

? si的文件支持內(nèi)置FileToByteArrayTransformer 和 FileToStringTransformer可將文件轉(zhuǎn)為byte[]或字符串。si file可以協(xié)助你方便地實(shí)現(xiàn)文件與消息之間的互相轉(zhuǎn)換,但是針對(duì)文件的更復(fù)雜處理能力還是比不上專業(yè)文件處理工具如Spring Batch.

? 最后,所有組件都在spring context里,任何時(shí)候都可以直接拿到,進(jìn)行操作(而且互相之間依然保持無耦合):

MessageChannel channel =context.getBean("Channel Bean ID", MessageChannel.class);

Message message = MessageBuilder.withPayload(payload).build();

channel.send(message, 10000);

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容