Springやってみた。
講座はこちら。短いし、シンプルでとっかかりにはすごくよかった。
構成は、
こんな感じのフォームに商品番号を入れると、商品名が返ってくるWebアプリ。
↓↓↓↓↓↓
作ったソースの一部。
コントローラー
package com.example.demo; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestMapping; @Controller public class ProductController { private final ProductService productService; //ProductServiceのDI。コンストラクタでDIする public ProductController(ProductService productService) { this.productService = productService; } @RequestMapping("/input") public String index(ProductForm productForm) { return "index.html"; } @RequestMapping("/output") public String result(@Validated ProductForm shainForm, BindingResult bindingResult, Model model) { if (bindingResult.hasErrors()) { return "index.html"; } model.addAttribute("number", shainForm.getNumber()); //サービス層から商品名を取得してくる String name = productService.findByNo(shainForm.getNumber()); model.addAttribute("name", name); return "output.html"; } }
サービス
package com.example.demo; import org.springframework.stereotype.Service; @Service //Bean化 public class ProductServiceImpl implements ProductService { private final ProductRepository productRepository; //RepositoryのDI public ProductServiceImpl(ProductRepository productRepository) { this.productRepository = productRepository; } @Override public String findByNo(String number) { //リポジトリから商品を選択 String name = productRepository.selectByNo(number); return name; } }
リポジトリ
package com.example.demo; import org.springframework.stereotype.Repository; @Repository //Bean化 public class ProductRepositoryImpl implements ProductRepository { @Override public String selectByNo(String number) { String name; // 実際はここがselect結果とかになる。 // java女子部で聞いたパターンマッチング使ってみた switch (number) { case "100" -> name = "ルマンド"; case "101" -> name = "ビスコ"; default -> name = "登録されていません"; } return name; } }
クラスにアノテーションをつけてBean化すると、何が嬉しいかというと、
こんなことができる、ということらしい。
package com.example.demo; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.Mockito.*; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) public class ProductServiceImplTest { @MockBean // モック化 private ProductRepository productRepository; @Test public void findByNoTest() throws Exception { // productRepositoryはmock化しているので、好きな戻り値を設定できる when(productRepository.selectByNo("100")).thenReturn("ビスコ"); ProductService productService = new ProductServiceImpl(productRepository); // サービスに100を渡すと、本来はリポジトリからテーブルにアクセスしてselectしてくる作りだが、 // ↑でモック化して100を渡すとビスコを返す!と定義しているので、その通りビスコを返してくれる。 // つまりリポジトリが未完成でも、呼び出し元のほうで挙動を仮定して、テストができる。 String employee = productService.findByNo("100"); // 結果、OKになる assertEquals(employee, "ビスコ"); } }
ソースコード全量はこちら。
GitHub - ta202301/TrySpringBoot: Introduction to Spring boot
感想
雰囲気は理解した気がする。
単純に思ったのが、newがないとそれだけでなんか楽。毎回newするのめんどいし、処理の本質には関係ないところなのでなるべく書きたくない。
モック化に関しては、Springじゃなくてもモック自体はできるから、
DIの本領はまだ他のところにありそうだなーと思った。
「依存性の注入」って言葉がいかついけど、触ってみるとなるほどね、って感じだった。
ところで、毎回newしないということは、マルチスレッドとかでバグるやつなのでは?と思ったら、やっぱりそうらしい。
上記で作ってみたリポジトリとかは、呼び出されるたびに生成されるようにしないといけない、ということなのかな。
ID=100でアクセスしたはずが、ID=101の結果が返ってきちゃう、ということになるとまずいよね。
え、シングルトンってじゃあ、どこで使うの・・?と思ったら、既にまとめてくれてる人がいた。
なるほど。実際に設計してみないとぴんとこなそうだなあ。
大きな業務アプリの設計って、やる機会なくて、鍛えるのむずかしい。。