EatSmartシステム部ブログ

ウェブサイトの開発や運営に関する情報です。

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マッパー)の固有の機能についても便利なものがあり、実務で触れることができて良かったと思いました。