Java
spring
spring-integration

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

More than 1 year has passed since last update.

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

Spring Integrationでは、

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

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

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を適用することができます。

JavaConfigによるBean定義例
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new MessageLoggingHandlerInterceptor())
                .addPathPatterns("/**") // 適用対象のパス(パターン)を指定する
                .excludePathPatterns("/static/**"); // 除外するパス(パターン)を指定する
    }
}
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の中でリクエストマッピングが管理され、HandlerInterceptorIntegrationRequestMappingHandlerMappingのBeanの中で管理されているものが適用される仕組みになっています。

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

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

JavaConfigによるBean定義例
@Bean
public MappedInterceptor customHandlerInterceptor() {
  return new MappedInterceptor(
      new String[]{"/**"},
      new String[]{"/static/**"},
      new MessageLoggingHandlerInterceptor());
}
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を指定する必要があります。

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による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使えないか試してみたという感じです。