ファイルを書く

ファイルシステムにメッセージを書き込むには、FileWritingMessageHandler (Javadoc) を使用できます。このクラスは、次のペイロード型を処理できます。

  • File

  • String

  • Byte 配列

  • InputStream ( バージョン 4.2 以降)

String ペイロードの場合、エンコードと文字セットを構成できます。

物事を簡単にするために、XML 名前空間を使用して、FileWritingMessageHandler を送信チャネルアダプターまたは送信ゲートウェイの一部として構成できます。

バージョン 4.3 以降、ファイルの書き込み時に使用するバッファーサイズを指定できます。

バージョン 5.1 以降では、FileExistsMode.APPEND または FileExistsMode.APPEND_NO_FLUSH を使用し、新しいファイルを作成する必要がある場合にトリガーされる BiConsumer<File, Message<?>> newFileCallback を提供できます。このコールバックは、新しく作成されたファイルとそれをトリガーしたメッセージを受け取ります。このコールバックは、たとえば、メッセージヘッダーで定義された CSV ヘッダーを書き込むために使用できます。

ファイル名の生成

最も単純な形式では、FileWritingMessageHandler はファイルを書き込むための宛先ディレクトリのみを必要とします。書き込まれるファイルの名前は、ハンドラーの FileNameGenerator (Javadoc) によって決定されます。デフォルトの実装 (Javadoc) は、FileHeaders.FILENAME (Javadoc) として定義された定数とキーが一致するメッセージヘッダーを探します。

あるいは、メッセージに対して評価される式を指定して、ファイル名を生成できます(例: headers['myCustomHeader'] + '.something')。式は String に評価される必要があります。便宜上、DefaultFileNameGenerator には setHeaderName メソッドも用意されており、ファイル名として使用される値を持つメッセージヘッダーを明示的に指定できます。

一度設定すると、DefaultFileNameGenerator は次の解決手順を使用して、特定のメッセージペイロードのファイル名を決定します。

  1. メッセージに対して式を評価し、結果が空でない String である場合、それをファイル名として使用します。

  2. それ以外の場合、ペイロードが java.io.File の場合、File オブジェクトのファイル名を使用します。

  3. それ以外の場合は、メッセージ ID にを追加して使用します。ファイル名として msg

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) の 2 番目のコンストラクター引数として使用されます。ただし、これまでは、ファイル名のみを想定して、子パス用のディレクトリ(mkdirs())を作成していませんでした。このアプローチは、ソースディレクトリに一致するようにファイルシステムツリーを復元する必要がある場合に役立ちます。たとえば、アーカイブを解凍し、ターゲットディレクトリ内のすべてのファイルを元の順序で保存する場合です。

出力ディレクトリの指定

ファイル送信チャネルアダプターとファイル送信ゲートウェイの両方は、出力ディレクトリを指定するための相互に排他的な 2 つの構成属性を提供します。

  • directory

  • directory-expression

Spring Integration 2.2 は directory-expression 属性を導入しました。

directory 属性の使用

directory 属性を使用すると、出力ディレクトリは固定値に設定されます。これは、FileWritingMessageHandler の初期化時に設定されます。この属性を指定しない場合は、directory-expression 属性を使用する必要があります。

directory-expression 属性の使用

SpEL を完全にサポートしたい場合は、directory-expression 属性を使用できます。この属性は、処理されるメッセージごとに評価される SpEL 式を受け入れます。出力ファイルディレクトリを動的に指定すると、メッセージのペイロードとそのヘッダーにフルアクセスできます。

SpEL 式は、Stringjava.io.File または org.springframework.core.io.Resource のいずれかに解決される必要があります。(後者は File に評価されます) さらに、結果の String または File はディレクトリを指している必要があります。directory-expression 属性を指定しない場合は、directory 属性を設定する必要があります。

auto-create-directory 属性の使用

デフォルトでは、宛先ディレクトリが存在しない場合、それぞれの宛先ディレクトリと存在しない親ディレクトリが自動的に作成されます。その動作を防ぐために、auto-create-directory 属性を false に設定できます。この属性は、directory 属性と directory-expression 属性の両方に適用されます。

directory 属性を使用していて、auto-create-directory が false である場合、Spring Integration 2.2 以降、次の変更が行われました。

アダプターの初期化時に宛先ディレクトリの存在を確認する代わりに、処理中の各メッセージに対してこの確認が実行されるようになりました。

さらに、auto-create-directory が true であり、ディレクトリがメッセージの処理の間に削除された場合、ディレクトリは処理されるメッセージごとに再作成されます。

既存の宛先ファイルの処理

ファイルを書き込み、宛先ファイルがすでに存在する場合、デフォルトの動作ではそのターゲットファイルが上書きされます。関連するファイル送信コンポーネントの mode 属性を設定することにより、この動作を変更できます。次のオプションがあります。

  • REPLACE (デフォルト)

  • REPLACE_IF_MODIFIED

  • APPEND

  • APPEND_NO_FLUSH

  • FAIL

  • IGNORE

Spring Integration 2.2 は、mode 属性と APPENDFAILIGNORE オプションを導入しました。
REPLACE

ターゲットファイルがすでに存在する場合、上書きされます。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 (Javadoc) がスローされます。

IGNORE

ターゲットファイルが存在する場合、メッセージペイロードは警告なしに無視されます。

一時ファイルのサフィックスを使用する場合(デフォルトは .writing)、最終ファイル名または一時ファイル名のいずれかが存在する場合、IGNORE オプションが適用されます。

APPEND_NO_FLUSH 使用時のファイルのフラッシュ

APPEND_NO_FLUSH モードは、バージョン 4.3 で追加されました。各メッセージの後にファイルが閉じられないため、これを使用するとパフォーマンスが向上します。ただし、これにより、障害発生時にデータが失われる可能性があります。

Spring Integration は、このデータ損失を軽減するためのいくつかのフラッシュ戦略を提供します。

  • flushInterval を使用します。この期間ファイルが書き込まれない場合、自動的にフラッシュされます。これは概算であり、今回は 1.33x まで可能です(平均 1.167x)。

  • 正規表現を含むメッセージをメッセージハンドラーの trigger メソッドに送信します。パターンに一致する絶対パス名を持つファイルはフラッシュされます。

  • ハンドラーにカスタム MessageFlushPredicate 実装を提供して、メッセージが trigger メソッドに送信されたときに実行されるアクションを変更します。

  • カスタム FileWritingMessageHandler.FlushPredicate または FileWritingMessageHandler.MessageFlushPredicate 実装を渡すことにより、ハンドラーの flushIfNeeded メソッドの 1 つを呼び出します。

述語は、開いているファイルごとに呼び出されます。詳細については、これらのインターフェースの 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 許可をサポートするファイルシステムにファイルを書き込むときに、送信チャネルアダプターまたはゲートウェイでそれらの許可を指定できます。このプロパティは整数であり、通常は使い慣れた 8 進数形式で提供されます。たとえば、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 (Javadoc) を参照してください。

「送信ゲートウェイ」は、最初にファイルを移動してから処理パイプラインを介して送信する場合に適しています。このような場合、ファイル名前空間の 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();
    }

}