Spring MVC で Controller を動的に切り替える - RequestMappingHandlerMapping のサブクラス利用
Spring MVC では、基本的に @RequestMapping
アノテーションで指定した URL パターンに合致する Controller のメソッドを実行し、org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping
クラスがその処理を担っています。
そこで、試しに RequestMappingHandlerMapping
のサブクラスを使って、実行対象の Controller (@RequestMapping
のパス違い) を動的に切り替えるようにしてみました。
サンプルソースは http://github.com/fits/try_samples/tree/master/blog/20150507/
はじめに
今回は、以下のように Query string (URL) の debug
パラメータ有無によって、実行する Controller を切り替える処理を RequestMappingHandlerMapping
のサブクラスで実現します。
URL | 実行するメソッド名 |
---|---|
/sample/xxx | SampleController.sample |
/sample/xxx?debug | DebugSampleController.sample |
ただし、Controller を切り替えなくても他にやり様はいくらでもありますので、本件の実用性は低いと思います。
実装
Controller クラス
まずは Controller を 2つ用意します。
src/main/java/sample/controller/SampleController.java
package sample.controller; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseBody; @Controller public class SampleController { @RequestMapping("/sample/{id}") @ResponseBody public String sample(@PathVariable("id") String id) { return "sample: " + id + ", " + System.currentTimeMillis(); } }
2つ目には @RequestMapping
パスの先頭に /debug
を付けました。
src/main/java/sample/controller/DebugSampleController.java
package sample.controller; ・・・ @Controller public class DebugSampleController { @RequestMapping("/debug/sample/{id}") @ResponseBody public String sample(@PathVariable("id") String id) { return "debug-sample: " + id + ", " + new Date(); } }
RequestMappingHandlerMapping サブクラス
次に、本題の RequestMappingHandlerMapping サブクラスを実装します。
とりあえず、下記のように実装すれば別の Controller を呼び出せます。
- (1)
lookupHandlerMethod
メソッドをオーラーライドし lookupPath を変更 - (2)
HttpServletRequest.getServletPath()
の値を (1) に合わせて変更
(1) を実施しただけでは内部的なパスのチェック処理に引っかかるので (2) も合わせて実施する必要があります。
下記では、Query string へ debug
が付いていた場合に Controller を選出するパス (lookupPath) の先頭に /debug
を追加するように実装し、getServletPath() の戻り値を変更するために HttpServletRequestWrapper のサブクラスを使っています。
src/main/java/sample/mapping/SampleRequestMappingHandlerMapping.java
package sample.mapping; import org.springframework.web.method.HandlerMethod; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; public class SampleRequestMappingHandlerMapping extends RequestMappingHandlerMapping { // (1) @Override protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { return super.lookupHandlerMethod( changePath(lookupPath, request), new SampleHttpServletRequest(request) ); } // Controller を選出するパスを書き換える private String changePath(String path, HttpServletRequest request) { if (request.getParameter("debug") != null) { return "/debug" + path; } return path; } class SampleHttpServletRequest extends HttpServletRequestWrapper { public SampleHttpServletRequest(HttpServletRequest req) { super(req); } // (2) @Override public String getServletPath() { return changePath(super.getServletPath(), this); } } }
設定クラス
SampleRequestMappingHandlerMapping
を適用するように Bean 定義を行います。
src/main/java/sample/config/WebConfig.java
package sample.config; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; import sample.mapping.SampleRequestMappingHandlerMapping; @Configuration @EnableWebMvc public class WebConfig { @Bean public RequestMappingHandlerMapping requestMappingHandlerMapping() { return new SampleRequestMappingHandlerMapping(); } }
実行クラス
今回は Spring Boot を使って実行しますので、そのための実行クラスも用意します。
src/main/java/sample/Application.java
package sample; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.context.annotation.ComponentScan; @ComponentScan @EnableAutoConfiguration public class Application { public static void main(String... args) { SpringApplication.run(Application.class, args); } }
ビルド定義
Gradle のビルド定義ファイルは以下の通りです。 (Spring Boot 利用)
build.gradle
apply plugin: 'spring-boot' def enc = 'UTF-8' tasks.withType(AbstractCompile)*.options*.encoding = enc buildscript { repositories { jcenter() } dependencies { classpath 'org.springframework.boot:spring-boot-gradle-plugin:1.2.3.RELEASE' } } repositories { jcenter() } dependencies { compile 'org.springframework.boot:spring-boot-starter-web:1.2.3.RELEASE' }
実行
bootRun
タスクを実行し、Tomcat 上で Web アプリケーションを起動しておきます。
起動
> gradle bootRun ・・・ :bootRun . ____ _ __ _ _ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \ ( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \ \\/ ___)| |_)| | | | | || (_| | ) ) ) ) ' |____| .__|_| |_|_| |_\__, | / / / / =========|_|==============|___/=/_/_/_/ :: Spring Boot :: (v1.2.3.RELEASE) ・・・ 2015-05-03 16:43:04.306 INFO 4860 --- [ main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http) 2015-05-03 16:43:04.308 INFO 4860 --- [ main] sample.Application : Started Application in 5.001 seconds (JVM running for 6.5)
/sample/xxx
と /sample/xxx?debug
へアクセスすると、?debug
の有無で実行結果 (実行対象 Controller) が変化する事を確認できました。
動作確認
$ curl http://localhost:8080/sample/abc sample: abc, 1430639107957 $ curl http://localhost:8080/sample/abc?debug debug-sample: abc, Sun May 03 16:45:12 JST 2015