今お仕事でSpring Integrationと戯れているのですが、Http.inboundGateway
(Java DSL)または<http-int:inbound-gateway>
(XMLネームスペース)を使ってBean定義したHandler(HttpRequestHandlingMessagingGateway
)に対してSpring MVCのHandlerInterceptor
が適用できなくてちょっとだけ困っていたのですが、IntegrationRequestMappingHandlerMapping
のソースを追ったら適用する方法がわかりました。
Spring Integrationでは、
@Bean
public IntegrationFlow greetingInboundGatewayFlow(MessageChannel httpInboundChannel) {
return IntegrationFlows.from(Http.inboundGateway("/greeting")
.requestMapping(mapping -> mapping.methods(HttpMethod.GET))
.requestChannel(httpInboundChannel)
).get();
}
<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
を適用することができます。
@Configuration
@EnableWebMvc
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new MessageLoggingHandlerInterceptor())
.addPathPatterns("/**") // 適用対象のパス(パターン)を指定する
.excludePathPatterns("/static/**"); // 除外するパス(パターン)を指定する
}
}
<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定義をすればよいだけでした。
@Bean
public MappedInterceptor customHandlerInterceptor() {
return new MappedInterceptor(
new String[]{"/**"},
new String[]{"/static/**"},
new MessageLoggingHandlerInterceptor());
}
<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
を指定する必要があります。
@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;
}
<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
使えないか試してみたという感じです。