SpringBootで遭遇したルーティング問題
今回は、SpringBootのプロジェクトの中で学んだことについてまとめていきます。 もぐナビのスマホページのamp対応として、別プロジェクトにSpringBootを使用しております。 簡単にプロジェクトの環境としては以下の編成になります。 SpringBoot 1.5.2 Thymeleaf 2.1.5 Doma 1.1.0
今回、実装した内容は以下のような食品ブランド×カテゴリのページのamp版になります。 https://s.mognavi.jp/amp/brand/2113/ranking/konbini-sandwich
使用したアノテーションについて
@RequestMapping コントローラーの処理の中でurlとの紐づけを行い、value属性でパスを指定します。 method = RequestMethod.GETとすることで、リクエストの形式をGETと指定する ことができます。@RequestMapping(value = {"/index" }) method属性を利用する場合には、@RequestMapping(path = "/input", method = RequestMethod.GET)と 書くことも可能です。
@PathVariable urlに含まれる値をパラメーターとして動的に取得する場合に利用ができます。 例えば、https://mognavi.jp/food/1(※foodIdと仮定)でアクセスしてきた場合に、 foodIdを取得して利用したい場合に、@PathVariable int foodIdとすることで1という値を取得できます。
SpringBootのルーティングで躓いたこと
最初に実装したパターン(エラーが起こる)
(既存コード) @RequestMapping(value = "/amp/brand/{key}/ranking/{rankingType}") ModelAndView ranking(@PathVariable String key, @PathVariable String rankingType) { 省略 } (追加コード) @RequestMapping(value = "/amp/brand/{key}/ranking/{categoryPath}") ModelAndView rankingCategory(@PathVariable String key, @PathVariable String categoryPath) { 省略 }
既存のページで既に/amp/brand/{key}/ranking/{rankingType}というパスでアクセスするものがありました。 ※keyには、ブランドに紐づいたコードが対応。 ※rankingTypeは、注目と評価ランキングに相当。注目→1、評価→2。
今回新規で追加したものは、/amp/brand/{key}/ranking/{categoryPath}というパスの形式になります。 ※keyには、上記の説明と同様の値。 ※categoryPathには、sweets(スイーツ),konbini-sandwich(コンビニサンドイッチ)など食品のカテゴリが対応。
この実装で、例えば/amp/brand/2113/ranking/konbini-sandwichへアクセスしてみると、 java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path 'http://localhost:8888/amp/brand/2113/ranking/konbini-sandwich' というエラーが返されました。同じ形式のパスが二つあると解釈されてしまいます。
試したこと
@PathVariableで渡すrankingTypeとcategoryPathという型が同一であるため、パスを識別できないのかと思いました。 rankingTypeをStringからIntegerに、categoryPathは、そのままStringに。@PathVariable Integer rankingType、もう一方は@PathVariable String categoryPathとすることでリクエストを別にしてくれることを期待しましたが、同じエラーが発生します。
最終的な解決策
@RequestMapping(value = "/amp/brand/{key}/ranking/{parameter}") ModelAndView ranking(@PathVariable String key, @PathVariable String parameter) { if ("1".equals(parameter) || "2".equals(parameter)) { return ranking(key, Integer.parseInt(parameter)); } return rankingCategory(key, parameter); } ModelAndView ranking(@PathVariable String key, @PathVariable String rankingType) { 省略 } ModelAndView rankingCategory(@PathVariable String key, @PathVariable String categoryPath) { 省略 }
}
コードの説明になりますが、1又は2がparameterに渡された場合には、rankingというビューの情報を返し、それ以外の場合にはrankingCategoryのビューを返すという方法をとりました。 こうすることで、パスがうまく識別され、@Pathvariableに渡る値によって異なるビューを返すようになりました。
補足
ちなみに、parameterをparamとすると以下のようにエラーとなりました。 java.lang.IllegalArgumentException: Putting a context variable with name "param" is forbidden, as it is a reserved variable name. param自体がThymeleafに由来するクラスの予約語として扱われているため、使用することができないようです。
まとめ
以上ですが、エラーの原因はメッセージから読み取れるものの、解決に至るまでが難しかったです。 また、今回は書きれませんでしたが、最初の構成で紹介したThymeleaf、Doma(O/Rマッパー)の固有の機能についても便利なものがあり、実務で触れることができて良かったと思いました。