はじめに Spring Boot 1.2.5 を使いはじめてハマった事をメモします。
既にご存じかとは思いますが、以下の Qiita がとても良くまとまっているので
ご紹介します。
#1.2.8 でも同じようにハマれるのを確認しています
SpringBoot(with Thymeleaf)チートシート[随時更新]
http://qiita.com/uzresk/items/31a4585f7828c4a9334f
Whitelabel Error Page を置き換えたい エラーメッセージだけを読むと、適当なコントローラーをつくって
/error のリクエストマッピングを作成すればよさそうに思えるが、
実際やってみると、マッピングが重複している的なエラーで起動すらできなくなる。
正解は、 /error をマッピングする Controller は ErrorController を
継承しなければならない。
こんな感じ。このコントローラーにエラーじゃないマッピングを含むことは OK。
当然だが、コンポーネントスキャンの範囲内に作らないとダメ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 <code class="language-java">@Controller public class ErrController implements ErrorController { private static final String PATH = "/error"; @RequestMapping("/404") String notFoundError() { return "error/404"; // templates/error/404.html } @RequestMapping(PATH) String home() { return "error/general"; } @Override public String getErrorPath() { return PATH; } }
実は /template/error.html を作れば自動的にそれが使用される。
※ 少なくとも Springboot 1.3.5 で確認
2017/01/27 追記
コメントでご指摘頂きましたが、 error.html を作成するとエラーページが置き換わるのは、Thymeleaf を
使用している時のみとのこと。 @syukai さんご指摘ありがとうございました。
Formatter を作って、そのクラスに @Component を付けるだけ。
@ComponentScan 範囲にいれば、自動的に登録される。
ググると FormattingConversionServiceFactoryBean 使って登録みたいな
事が書いてあるが、Springboot では自動登録される。
#逆に、自動登録されては困るときは色々と考えないといけないんでしょう
これ、意外とどこにも無くてハマったのですが、Form Validation は普通に
Springboot に付いてます。カスタムバリデーションも書けます。
それは良いけれど、フォーム画面を表示するのに model に何かをセットしないと
いけない場合の事は意外と書かれてません。これで正しいのかはわかりませんが、
フォーム表示時のメソッドを普通のメソッドとして呼び出せば上手くいきます。
親切なことに、フォームオブジェクト上のバリデーションに失敗した項目は
null が入っていますが、それを上書きして別の値を入れたとしても無視され、
入力した(結果エラーとなった値)が画面に表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 @RequestMapping(value="", method = RequestMethod.GET) String showForm (@ModelAttribute TestForm form, Model model) { form.setValue("default" ); model.addAttribute("dbdata" , hoge.getData()); return "testview" ; } @RequestMapping(value="", method = RequestMethod.POST) String formPost (@ModelAttribute TestForm form, BindingResult result,Model model) { if (result.hasErrors()) { return showForm(form, model); return "redirect:/testForm" ; return "redirect:testview" ; } (略) return "completeview" ; }
1 2 3 4 5 6 7 8 <form method ="post" th:action ="@{/test}" th:object ="${testForm}" > <input type ="text" name ="value" class ="form-control" th:field ="*{value}" /> エラー表示は省略。 </form >
ControllerAdvice で例外を拾って処理をしたらステータスコードが 405 @ResponseBody の付け忘れ。これが付いてないとテンプレートを探しに行って
しまっている模様。
1 `o.s.web.servlet.PageNotFound : Request method 'POST' not supported
これ、 @ResponseStatus を平然と無視するのでかなり焦った。
RestController で Ajax でリクエストする API を作ったが実装部より前で例外 CSRF フィルタに引っかかってないかチェック。
Java 8 Date and Time を JSON にシリアライズするとき例外 jackson JSR-310 を入れれば良い。
pom.xml に以下を追加
1 2 3 4 <dependency > <groupId > com.fasterxml.jackson.datatype</groupId > <artifactId > jackson-datatype-jsr310</artifactId > </dependency >
セキュリティ的にあえて復元しないようになっているのかもしれない。
回避するには、とりあえず、 input type=”text” としておいて、JS で切替。
1 2 3 $(document ).ready (function ( ) { $("#password" ).attr ("type" , "password" ); });
BindingResult を書いたつもりなのに、そこまで処理が来ない。
理由は簡単、BindingResult は @Valid なフォームの次に書かないと無効。
1 2 3 4 5 6 7 @RequestMapping String hoge (@Valid TestForm form, Model model, BindingResult result) {} @RequestMapping String hoge (@Valid TestForm form, BindingResult result, Model model) {}
デフォルトでは全ての Bean がシングルトン Controller も、Service もシングルトン。
と言うことは、インスタンス変数に状態を保存すると他のリクエストの情報と混ざる可能性がある。
Service に @Scope(“prototype”) を指定すれば良いかというとそうではない。
prototype は要求されたら新しいインスタンスを生成して返す。という動作だが、そもそも
Controller が Signleton なので、最初に生成されたインスタンスがずっと使われ続ける。
基本的には Singleton で動作しても問題ないように作るべき。
※Singleton で動いても問題ない(=スレッドセーフ)に作る為にはどうすれば良いのか?
※ご参考:http://qiita.com/yoshi-naoyuki/items/507c5c3ea6027033f4bb
HttpSession に関しては特段の配慮がされているので、フィールドに入れて@Autowired しても OK
ファイルアップロードしようとしたらファイルが入ってこない http://blog.okazuki.jp/entry/2015/07/17/202941
上記 URL のように正しく書いたつもりが、Form の MultiFile が null になる。
原因は簡単、 form タグに enctype=”multipart/form-data” を書き忘れるとこうなる。
ログを見ると、 String から MultiFile にコンバートできなかった的なエラーが出ている。
th:field を使うと、form 内容と HTML 上のフォームの値が紐付いてしまう。
th:value で値を入れたい場合は、th:field ではなく、普通に name=”name”で名前を合わせる。
1 2 3 4 5 <input type ="hidden" th:field ="*{somefield}" th:value ="${bean.value}" /> <input type ="hidden" name ="somefield" th:value ="${bean.value}" />
ビューを表示するたびに Velocity がエラーを吐く hoge.vm が存在しません的なもの。Velocity のオートコンフィグをしないように
しないといけないらしい。
1 2 <code class="language-java">@SpringBootApplication @EnableAutoConfiguration(exclude = { DataSourceAutoConfiguration.class,VelocityAutoConfiguration.class })
logger 出力をフォーマットしたい logger.debugFormat(“value of A is {0}”,A); みたいなの。
slf4j を使っているなら、次の通り。
1 <code class="language-java">logger.debugFormat("value of A is {}",A);
http://www.slf4j.org/faq.html#logging_performance
application.yml にゼロ埋めの数字を書くと 8 進数扱いされる そのまんま。
なんて書いたりすると、その値が 8 進数扱いされてしまう。文字列あたりにするのが良い。
jar にパッケージングした時だけ TemplateError が発生する Thymeleaf のテンプレートのパスを指定する際に、 / から始めるとそうなる。
これは、 th:include や th:replace のパスについても同様。
SpringBoot 1.5.x でも修正されていない。(今後も修正されない模様)
https://github.com/spring-projects/spring-boot/issues/1744
何がタチ悪いかというと、IDE 上で動かしているときはファイルシステムからテンプレートを
読むため、普通に動いてしまう。 jar にパッケージングした際に突如に動かなくなる。
pom.xml に以下を追記
詳しくは、http://qiita.com/IsaoTakahashi/items/f99d5f761d1d4190860d
1 2 3 4 <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-devtools</artifactId > </dependency >
ただし、LiveReload 機能と Spring-Loaded を一緒に使っても、LiveReload が動いてしまうので
排他利用になりそう。個人的には Spring-Loaded の方が好き。
でも、他にも便利機能があるので dev-tools の方がよいかなとは思った。
Request method ‘POST’ not supported @RequestMapping がちゃんとあるか確認するのが第一。
ちゃんとあるのであれば、CSRF フィルタに引っかかっている可能性が大。
th:action がないと、CSRF トークンが埋め込まれない。Javascript で動的に変えるにしても
ダミーの URL を書いておく必要がある。
1 2 3 4 5 <form th:object ="${HogeForm}" > これはダメパターン</form > <form th:action ="@{'/dummy'} th:object=" ${HogeForm }"> こうすればOK。トークンが自動で埋め込まれる。 </form >