寫入檔案
要將訊息寫入檔案系統,可以使用 FileWritingMessageHandler。此類可以處理以下有效載荷型別:
-
檔案 -
字串 -
位元組陣列
-
InputStream(自版本 4.2起)
對於 String 有效載荷,可以配置編碼和字元集。
為了簡化操作,可以將 FileWritingMessageHandler 配置為出站通道介面卡或出站閘道器的一部分,透過使用 XML 名稱空間。
從版本 4.3 開始,您可以指定寫入檔案時使用的緩衝區大小。
從版本 5.1 開始,您可以提供一個 BiConsumer<File, Message<?>> newFileCallback,當您使用 FileExistsMode.APPEND 或 FileExistsMode.APPEND_NO_FLUSH 並且必須建立新檔案時,此回撥將被觸發。此回撥接收新建立的檔案和觸發它的訊息。例如,此回撥可用於寫入訊息頭中定義的 CSV 頭部。
生成檔名
在其最簡單的形式中,FileWritingMessageHandler 只需要一個用於寫入檔案的目標目錄。要寫入的檔名由處理程式的 FileNameGenerator 決定。預設實現查詢與定義為 FileHeaders.FILENAME 的常量鍵匹配的訊息頭。
或者,您可以指定一個針對訊息進行評估以生成檔名的表示式——例如,headers['myCustomHeader'] + '.something'。該表示式必須評估為 String。為方便起見,DefaultFileNameGenerator 還提供了 setHeaderName 方法,允許您顯式指定其值將用作檔名的訊息頭。
設定完成後,DefaultFileNameGenerator 採用以下解析步驟來確定給定訊息有效載荷的檔名:
-
針對訊息評估表示式,如果結果是非空
String,則將其用作檔名。 -
否則,如果有效載荷是
java.io.File,則使用File物件的 檔名。 -
否則,使用附加了 .
msg的訊息 ID 作為檔名。
當您使用 XML 名稱空間支援時,檔案出站通道介面卡和檔案出站閘道器都支援以下互斥配置屬性:
-
filename-generator(對FileNameGenerator實現的引用) -
filename-generator-expression(評估為String的表示式)
在寫入檔案時,使用一個臨時檔案字尾(其預設值為 .writing)。在檔案寫入時,它會附加到檔名後面。要自定義字尾,您可以在檔案出站通道介面卡和檔案出站閘道器上設定 temporary-file-suffix 屬性。
當使用 APPEND 檔案 mode 時,temporary-file-suffix 屬性將被忽略,因為資料直接附加到檔案中。 |
從版本 4.2.5 開始,生成的檔名(作為 filename-generator 或 filename-generator-expression 評估的結果)可以表示一個子路徑和目標檔名。它像以前一樣用作 File(File parent, String child) 的第二個建構函式引數。然而,過去我們沒有為子路徑建立(mkdirs())目錄,只假定檔名。這種方法對於需要恢復檔案系統樹以匹配源目錄的情況很有用——例如,解壓縮存檔並將所有檔案按原始順序儲存在目標目錄中。
指定輸出目錄
檔案出站通道介面卡和檔案出站閘道器都提供了兩個互斥的配置屬性來指定輸出目錄:
-
目錄 -
目錄表示式
Spring Integration 2.2 引入了 directory-expression 屬性。 |
使用 directory 屬性
當您使用 directory 屬性時,輸出目錄被設定為固定值,該值在 FileWritingMessageHandler 初始化時設定。如果您不指定此屬性,則必須使用 directory-expression 屬性。
使用 directory-expression 屬性
如果您想擁有完整的 SpEL 支援,可以使用 directory-expression 屬性。此屬性接受一個 SpEL 表示式,該表示式針對每個正在處理的訊息進行評估。因此,當您動態指定輸出檔案目錄時,您可以完全訪問訊息的有效載荷及其頭。
SpEL 表示式必須解析為 String、java.io.File 或 org.springframework.core.io.Resource。(後者無論如何都會評估為 File。)此外,生成的 String 或 File 必須指向一個目錄。如果您不指定 directory-expression 屬性,那麼您必須設定 directory 屬性。
使用 auto-create-directory 屬性
預設情況下,如果目標目錄不存在,則會自動建立相應的目標目錄和任何不存在的父目錄。要防止此行為,可以將 auto-create-directory 屬性設定為 false。此屬性適用於 directory 和 directory-expression 屬性。
|
當使用 不再在介面卡初始化時檢查目標目錄是否存在,現在在處理每個訊息時執行此檢查。 此外,如果 |
處理現有目標檔案
當您寫入檔案並且目標檔案已存在時,預設行為是覆蓋該目標檔案。您可以透過設定相關檔案出站元件上的 mode 屬性來更改此行為。存在以下選項:
-
REPLACE(預設) -
REPLACE_IF_MODIFIED -
APPEND -
APPEND_NO_FLUSH -
FAIL -
IGNORE
Spring Integration 2.2 引入了 mode 屬性以及 APPEND、FAIL 和 IGNORE 選項。 |
替換-
如果目標檔案已存在,則覆蓋它。如果未指定
mode屬性,這是寫入檔案時的預設行為。 REPLACE_IF_MODIFIED-
如果目標檔案已存在,則僅當其上次修改時間戳與原始檔不同時才覆蓋它。對於
File有效載荷,有效載荷的lastModified時間與現有檔案進行比較。對於其他有效載荷,FileHeaders.SET_MODIFIED(file_setModified) 頭與現有檔案進行比較。如果缺少頭或其值不是Number,則始終替換檔案。 APPEND-
此模式允許您將訊息內容附加到現有檔案而不是每次都建立新檔案。請注意,此屬性與
temporary-file-suffix屬性互斥,因為當它將內容附加到現有檔案時,介面卡不再使用臨時檔案。檔案在每條訊息後關閉。 APPEND_NO_FLUSH-
此選項與
APPEND具有相同的語義,但資料不會在每條訊息後重新整理,檔案也不會關閉。這可以在發生故障時提供顯著的效能,但存在資料丟失的風險。有關更多資訊,請參閱 使用APPEND_NO_FLUSH時重新整理檔案。 FAIL-
如果目標檔案存在,則丟擲
MessageHandlingException。 IGNORE-
如果目標檔案存在,則訊息有效載荷被靜默忽略。
當使用臨時檔案字尾(預設值為 .writing)時,如果最終檔名或臨時檔名存在,則應用 IGNORE 選項。 |
使用 APPEND_NO_FLUSH 時重新整理檔案
APPEND_NO_FLUSH 模式在 4.3 版本中新增。使用它可以提高效能,因為檔案在每條訊息後不會關閉。但是,這可能導致在發生故障時資料丟失。
Spring Integration 提供了幾種重新整理策略來減輕這種資料丟失:
-
使用
flushInterval。如果檔案在此期間未被寫入,它將自動重新整理。這只是近似值,可能高達該時間的1.33倍(平均1.167倍)。 -
向訊息處理程式的
trigger方法傳送包含正則表示式的訊息。絕對路徑名與模式匹配的檔案將被重新整理。 -
向處理程式提供自定義
MessageFlushPredicate實現,以修改將訊息傳送到trigger方法時所採取的操作。 -
透過傳遞自定義
FileWritingMessageHandler.FlushPredicate或FileWritingMessageHandler.MessageFlushPredicate實現來呼叫處理程式的flushIfNeeded方法之一。
謂詞將針對每個開啟的檔案呼叫。有關這些介面的更多資訊,請參閱 Javadoc。請注意,自版本 5.0 起,謂詞方法提供了另一個引數:當前檔案是新建還是以前關閉時首次寫入的時間。
當使用 flushInterval 時,間隔從上次寫入開始。僅當檔案空閒該間隔時才重新整理。從版本 4.3.7 開始,可以將附加屬性 (flushWhenIdle) 設定為 false,這意味著間隔從首次寫入已重新整理(或新)檔案開始。
檔案時間戳
預設情況下,目標檔案的 lastModified 時間戳是檔案建立的時間(除了就地重新命名保留當前時間戳)。從版本 4.3 開始,您現在可以配置 preserve-timestamp(或在使用 Java 配置時設定 setPreserveTimestamp(true))。對於 File 有效載荷,這會將時間戳從入站檔案傳輸到出站檔案(無論是否需要複製)。對於其他有效載荷,如果存在 FileHeaders.SET_MODIFIED 頭 (file_setModified),只要頭是 Number 型別,就用它來設定目標檔案的 lastModified 時間戳。
檔案許可權
從版本 5.0 開始,當將檔案寫入支援 Posix 許可權的檔案系統時,您可以在出站通道介面卡或閘道器上指定這些許可權。該屬性是一個整數,通常以熟悉的八進位制格式提供——例如,0640,表示所有者具有讀/寫許可權,組具有隻讀許可權,其他使用者無權訪問。
檔案出站通道介面卡
以下示例配置了一個檔案出站通道介面卡:
<int-file:outbound-channel-adapter id="filesOut" directory="${input.directory.property}"/>
基於名稱空間的配置還支援 delete-source-files 屬性。如果設定為 true,它會在寫入目標後觸發原始原始檔的刪除。該標誌的預設值為 false。以下示例展示瞭如何將其設定為 true:
<int-file:outbound-channel-adapter id="filesOut"
directory="${output.directory}"
delete-source-files="true"/>
delete-source-files 屬性僅在入站訊息具有 File 有效載荷或 FileHeaders.ORIGINAL_FILE 頭值包含源 File 例項或表示原始檔案路徑的 String 時才有效。 |
從版本 4.2 開始,FileWritingMessageHandler 支援 append-new-line 選項。如果設定為 true,則在寫入訊息後會將新行附加到檔案中。預設屬性值為 false。以下示例展示瞭如何使用 append-new-line 選項:
<int-file:outbound-channel-adapter id="newlineAdapter"
append-new-line="true"
directory="${output.directory}"/>
出站閘道器
在您希望根據寫入的檔案繼續處理訊息的情況下,您可以使用 outbound-gateway。它扮演的角色與 outbound-channel-adapter 類似。但是,在寫入檔案後,它還會將其作為訊息的有效載荷傳送到回覆通道。
以下示例配置了一個出站閘道器:
<int-file:outbound-gateway id="mover" request-channel="moveInput"
reply-channel="output"
directory="${output.directory}"
mode="REPLACE" delete-source-files="true"/>
如前所述,您還可以指定 mode 屬性,它定義瞭如何處理目標檔案已存在的情況的行為。有關更多詳細資訊,請參閱 處理現有目標檔案。通常,當使用檔案出站閘道器時,結果檔案作為訊息有效載荷返回到回覆通道。
這也適用於指定 IGNORE 模式的情況。在這種情況下,返回預先存在的目標檔案。如果請求訊息的有效載荷是一個檔案,您仍然可以透過訊息頭訪問該原始檔案。請參閱 FileHeaders.ORIGINAL_FILE。
“出站閘道器”在您希望首先移動檔案,然後透過處理管道傳送檔案的情況下非常有效。在這種情況下,您可以將檔案名稱空間的 inbound-channel-adapter 元素連線到 outbound-gateway,然後將該閘道器的 reply-channel 連線到管道的開頭。 |
如果您有更復雜的要求或需要支援額外的有效載荷型別作為輸入以轉換為檔案內容,您可以擴充套件 FileWritingMessageHandler,但一個更好的選擇是依賴 Transformer。
使用 Java 配置
以下 Spring Boot 應用程式展示瞭如何使用 Java 配置入站介面卡的示例
@SpringBootApplication
@IntegrationComponentScan
public class FileWritingJavaApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new SpringApplicationBuilder(FileWritingJavaApplication.class)
.web(false)
.run(args);
MyGateway gateway = context.getBean(MyGateway.class);
gateway.writeToFile("foo.txt", new File(tmpDir.getRoot(), "fileWritingFlow"), "foo");
}
@Bean
@ServiceActivator(inputChannel = "writeToFileChannel")
public MessageHandler fileWritingMessageHandler() {
Expression directoryExpression = new SpelExpressionParser().parseExpression("headers.directory");
FileWritingMessageHandler handler = new FileWritingMessageHandler(directoryExpression);
handler.setFileExistsMode(FileExistsMode.APPEND);
return handler;
}
@MessagingGateway(defaultRequestChannel = "writeToFileChannel")
public interface MyGateway {
void writeToFile(@Header(FileHeaders.FILENAME) String fileName,
@Header(FileHeaders.FILENAME) File directory, String data);
}
}
使用 Java DSL 進行配置
以下 Spring Boot 應用程式展示瞭如何使用 Java DSL 配置入站介面卡的示例:
@SpringBootApplication
public class FileWritingJavaApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context =
new SpringApplicationBuilder(FileWritingJavaApplication.class)
.web(false)
.run(args);
MessageChannel fileWritingInput = context.getBean("fileWritingInput", MessageChannel.class);
fileWritingInput.send(new GenericMessage<>("foo"));
}
@Bean
public IntegrationFlow fileWritingFlow() {
return IntegrationFlow.from("fileWritingInput")
.enrichHeaders(h -> h.header(FileHeaders.FILENAME, "foo.txt")
.header("directory", new File(tmpDir.getRoot(), "fileWritingFlow")))
.handle(Files.outboundGateway(m -> m.getHeaders().get("directory")))
.channel(MessageChannels.queue("fileWritingResultChannel"))
.get();
}
}