Springやってみた①

Springやってみた。
講座はこちら。短いし、シンプルでとっかかりにはすごくよかった。

www.udemy.com

構成は、
こんな感じのフォームに商品番号を入れると、商品名が返ってくる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しないということは、マルチスレッドとかでバグるやつなのでは?と思ったら、やっぱりそうらしい。

qiita.com

上記で作ってみたリポジトリとかは、呼び出されるたびに生成されるようにしないといけない、ということなのかな。
ID=100でアクセスしたはずが、ID=101の結果が返ってきちゃう、ということになるとまずいよね。

え、シングルトンってじゃあ、どこで使うの・・?と思ったら、既にまとめてくれてる人がいた。

qiita.com

なるほど。実際に設計してみないとぴんとこなそうだなあ。
大きな業務アプリの設計って、やる機会なくて、鍛えるのむずかしい。。