Spring Integrationのhttp inbound-gatewayにHandlerInterceptorを適用する方法

今お仕事でSpring Integrationと戯れているのですが、`Http.inboundGateway`(Java DSL)または`<http-int:inbound-gateway>`(XMLネームスペース)を使ってBean定義したHandler(`HttpRequestHandlingMessagingGateway`)に対してSpring MVCの`HandlerInterceptor`が適用できなくてちょっとだけ困っていたのですが、`IntegrationRequestMappingHandlerMapping`のソースを追ったら適用する方法がわかりました。

Spring Integrationでは、

```java:JavaDSLを使用したHandlerのBean定義例
@Bean
public IntegrationFlow greetingInboundGatewayFlow(MessageChannel httpInboundChannel) {
  return IntegrationFlows.from(Http.inboundGateway("/greeting")
      .requestMapping(mapping -> mapping.methods(HttpMethod.GET))
      .requestChannel(httpInboundChannel)
  ).get();
}
```

```xml:XMLネームスペースを使用したHandlerのBean定義例
<http-int:inbound-gateway
    path="/greeting" supported-methods="GET"
    request-channel="httpRequestChannel"/>
```

といったBean定義を行うことで、

```java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/greeting")
@RestController
public class GreetingRestController {
  @GetMapping
  public String greeting() {
    // ...
    return responseMessage;
  }
}
```

というControllerを作成した時と同じエンドポイントを公開することができます。


# 通常のSpring MVC(`@RequestMapping`ベースのHandler)だと・・・

Controllerクラスに`@RequestMapping`メソッドを実装するスタイルであれば、以下のようなBean定義をすることで`HandlerInterceptor`を適用することができます。

```java:JavaConfigによるBean定義例
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MessageLoggingHandlerInterceptor())
                .addPathPatterns("/**") // 適用対象のパス(パターン)を指定する
                .excludePathPatterns("/static/**"); // 除外するパス(パターン)を指定する
    }
}
```

```xml:XMLによるBean定義例
<mvc:interceptors>
  <mvc:interceptor>
    <mvc:mapping path="/**"/>
    <mvc:exclude-mapping path="/static/**"/>
    <bean class="com.example.MessageLoggingHandlerInterceptor"/>
  </mvc:interceptor>
</mvc:interceptors>
```

# Spring Integrationのhttp inbound-gatewayだと・・・

Spring Integrationのhttp inbound-gatewayを使用して定義したHandlerは、通常のSpring MVCとは別にリクエストマッピングが管理されます。具体的には`IntegrationRequestMappingHandlerMapping`というクラスから作られたBeanの中でリクエストマッピングが管理され、`HandlerInterceptor`も`IntegrationRequestMappingHandlerMapping`のBeanの中で管理されているものが適用される仕組みになっています。

# Spring Integrationのhttp inbound-gatewayに`HandlerInterceptor`を適用するには・・・

`IntegrationRequestMappingHandlerMapping`(正確には`IntegrationRequestMappingHandlerMapping`の親クラスである`AbstractHandlerMapping`)には、DIコンテナから`HandlerInterceptor`(正確には`HandlerInterceptor`と「適用パス」「除外パス」を保持する`MappedInterceptor`)を自動で検出してくれる仕組みが備わっているため、`HandlerInterceptor`のBean定義をすればよいだけでした。

```java:JavaConfigによるBean定義例
@Bean
public MappedInterceptor customHandlerInterceptor() {
  return new MappedInterceptor(
      new String[]{"/**"},
      new String[]{"/static/**"},
      new MessageLoggingHandlerInterceptor());
}
```

```xml:XMLによるBean定義例
<bean class="org.springframework.web.servlet.handler.MappedInterceptor">
  <constructor-arg name="includePatterns" value="/**"/>
  <constructor-arg name="excludePatterns" value="/static/**"/>
  <constructor-arg name="interceptor">
    <bean class="com.example.MessageLoggingHandlerInterceptor"/>
  </constructor-arg>
</bean>
```

ただし・・・`HanderInterceptor`を複数定義し、かつ適用順序を制御する必要がある場合は、Bean定義順＝適用順になる保証はないため、`IntegrationRequestMappingHandlerMapping`のBeanを定義して明示的に`HanderInterceptor`を指定する必要があります。

```java:JavaConfigによるBean定義
@Bean
public IntegrationRequestMappingHandlerMapping integrationRequestMappingHandlerMapping() { // Bean名はintegrationRequestMappingHandlerMappingにする必要あり
  IntegrationRequestMappingHandlerMapping mapping = new IntegrationRequestMappingHandlerMapping();
  mapping.setOrder(0); // orderは0にする必要あり
  mapping.setInterceptors( // MappedInterceptorを適用したい順で追加する
      new MappedInterceptor(new String[] {"/**"}, new String[] {"/static/**"},
          new CustomHandlerInterceptor()),
      new MappedInterceptor(new String[] {"/**"}, new String[] {"/static/**"},
          new MessageLoggingHandlerInterceptor()));
  return mapping;
}
```

```xml:XMLによるBean定義例
<bean id="integrationRequestMappingHandlerMapping"
      class="org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping">
  <property name="order" value="0"/>
  <property name="interceptors">
    <array>
      <bean class="org.springframework.web.servlet.handler.MappedInterceptor">
        <constructor-arg name="includePatterns" value="/**"/>
        <constructor-arg name="excludePatterns" value="/static/**"/>
        <constructor-arg name="interceptor">
          <bean class="com.example.CustomHandlerInterceptor"/>
        </constructor-arg>
      </bean>
      <bean class="org.springframework.web.servlet.handler.MappedInterceptor">
        <constructor-arg name="includePatterns" value="/**"/>
        <constructor-arg name="excludePatterns" value="/static/**"/>
        <constructor-arg name="interceptor">
          <bean class="com.example.MessageLoggingHandlerInterceptor"/>
        </constructor-arg>
      </bean>
    </array>
  </property>
</bean>
```
# まとめ

HTTP向けのエンドポイント公開するなら普通にController作ればいいじゃん！！という話はありますが、今携わっている案件ではHTTP以外のプロトコルもサポートする必要があるため、Spring Integrationを使ってシステム間連携のアーキテクチャを合わせようとしています。
基本的にはSpring Integrationの世界の中で共通処理なども行おうと思っていますが、いくつかの処理（通信ログ出力など）はSpring Integrationの世界に入る前に行う必要がでてきそうなので、`HandlerInterceptor`使えないか試してみたという感じです。