---
# System prepended metadata

title: Spring Boot 零基礎入門學習筆記

---

# Spring Boot 零基礎入門學習筆記
## Spring IOC
### Spring IoC優點
IoC: 將物件的控制權，交由外部的Spring容器管理。
1. Loose coupling 鬆耦合
2. Lifecycle management 生命週期管理
3. More testable 方便測試程式
DI: 透過外部容器取得物件。
* 實作
    @Component
    加在class上，將class變成由Spring容器管理的bean
    @Autowired
    加在變數上，取得Spring容器中的bean(Dependency Injection)
### Bean的注入
@Autowired: 根據變數的類型在Spring容器中尋找符合類型的bean。
@Qualifier:
當同時有兩個class實作相同的介面，使用@Qualifier("{bean}")指定bean
bean: Class首字大寫轉小寫
### Bean的創建
1. @Configuration + @Bean
@Configuration: 表示這個class是用來設定Spring的。
@Bean: 只能加在帶有@Configueation class的方法上 
2. 前述的@Component的方法創建Bean
### Bean的初始化
1. 使用@PostConstruct
3. 實作InitializingBean interface的afterPropertiesSet()方法

#### @PostConstruct
設定條件
1. public 
2. void
3. 無參數傳入

### Bean的生命週期
1. 創建->初始化->拿來使用
2. 創建時或有依賴其他的bean, Spring會回頭創建+初始化那個被依賴的bean
3. 避免循環依賴

### Spring Boot 設定檔
application.properties
@Value("${key:default_value}")

## Spring AOP
### Spring AOP(Aspect-Oriented Programming切面導向程式設計)
1. POM.xml: 引入spring-boot-starter-aop
2. 創建介面的方法
@Aspect+@Component: 加在class上搭配@Component使用
宣告這個class是一個切面
3. 常用的切面註解：@Before/@After/@Around:加在宣告的方法上
4. Spring AOP發展
    1. 權限驗證: Spring Security
    2. 統一的Exception處理: @ControllerAdvice
    3. Log記錄
## Spring MVC
### @RequestMapping
用法：加在class或是方法上，小括號內填寫url路徑。
用途：將url路徑對應到方法上。
*註記：class上一定要加上@Controller/@RestController*
### @Controller/@RestController
用法：只加在class
用途：將該class變成bean，並且可使用@RequestMapping(@Component加強版)
## REST-ful API
* 目的： 簡化工程師之間的溝通成本
* 設計的API符合REST風格，那麼設計的API就是RESTful API，
* **不是一個規範**，使用最適當的作法即可。
### 取得請求參數
1. @RequestParam: 
    * 用法：只能加在方法的參數上
    * 用途：取得URL裡面的參數(query parameter)
    * 可使用的設定
        * name(or value):指定url參數的名字
        * required:是否是必須的參數，預設是true
        * defaultValue:required=false的加強版，提供預設值。
    * URL上多傳的參數會被spring boot忽略
    ```=java
    @RequestMapping("/test1")
    public String test1(@RequestParam Integer id,
                        @RequestParam(defaultValue = "Nick") String name) {
        System.out.println("id: " + id);
        System.out.println("name: " + name);
        return "Hello test1";
    }
    ```
1. @RequestBody
    * 用法：只能加在方法的參數上
    * 用途：取得request body裡面的參數(將Json轉為Java Object) 
    ```=java
    @RequestMapping("/test2")
    public String test2(@RequestBody Student student) {
        System.out.println(student);
        return "Hello test2";
    }
    ```
1. @RequestHeader
    * 用法：只能加在方法的參數上
    * 用途：取得request header裡面的參數
    * 可使用的設定
        * name(or value):指定header key的名字(常用：因為header常有橫線-)
        * required:是否是必須的參數，預設是true
        * defaultValue:required=false的加強版，提供預設值。
    * 常見的request header：

    | Request header | 意義                   | 常見的值 |
    | -------------- | ---------------------- | -------- |
    | Content-Type   | 表示request body的格式 | application/json(Json格式，最常用), application/octect-stream(用於上傳文件), multipart/form-data(用於上傳圖片)     |
    | Authorization  | 用於身份驗證           |      |

    ```=java
    @RequestMapping("/test3")
    public String test3(@RequestHeader String info) {
        System.out.println("info: " + info);
        return "Hello Test3";
    }
    ```

1. @PathVariable
    * 用法：只能加在方法的參數上
    * 用途：取得url路徑的值
    ```=java
    @RequestMapping("/test4/{id}")
    public String test4(@PathVariable Integer id) {
        System.out.println("id: " + id);
        return "Hello Test4";
    }
    ```
上述4種可以混用
### 回應狀態

1. Spring Boot預設返回的http狀態碼
    1. 正常執行完方法：返回200
    2. 噴出exception，返回500
1. ResponseEntity<?>
    * 用法：作為方法的返回類型
    * 用途：自定義回傳的http response的細節
```=java
@RequestMapping("/test")
public ResponseEntity<String> test() {
    return ResponseEntity.status(HttpStatus.ACCEPTED)
                         .body("Hello World");
}
```
1. @ControllerAdvice
    * 用法：只能加在class上
    * 用途：將這個class變成一個bean，並且可以在內部使用@ExceptionHandler
    * 底層由Spring AOP實作。

1. @ExceptionHandler
```=java
@ControllerAdvice
public class MyExceptionHandler {
    @ExceptionHandler(RuntimeException.class)
    public ResponseEntity<String> handle(RuntimeException exception) {
        return ResponseEntity.status(HttpStatus.SERVICE_UNAVAILABLE)
                .body("RuntimeException:" + exception.getMessage());
    }
}
```
### 攔截器(Interceptor)
1. 用途：決定要不要允許這次的 http request 通過，進到 Controller 裡去執行對應的方法
2. Config設定
```=java
@Configuration
public class MyConfig implements WebMvcConfigurer {
    @Autowired
    private MyInterceptor myInterceptor;
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(myInterceptor).addPathPatterns("/**");
    }
}
```
3. preHandle實作
```=java
@Component
public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("執行MyInterceptor的preHandle方法");
        response.setStatus(401);
        return false;
    }
}
```
### REST風格
1. 使用http method表示動作

| Http method | 對應的資料庫操作 | 說明 |
| -------- | -------- | -------- |
| POST     | **C**reate(新**增**)     | 新增一個資源     |
| GET     | **R**ead(**查**詢)     | 取得一個資源     |
| PUT     | **U**pdate(**修**改)     | 更新一個已存在的資源     |
| DELETE     | **D**elete(**刪**除)     | 刪除一個資源     |

2. 使用url路徑描述資源之間的階層(/)關係

| Http method + url路徑 | 說明                       |
| --------------------- | -------------------------- |
| GET/users             | 取得所有user               |
| GET/users/123         | 取得user id 為 123 的 user |
| GET/users/123/articles| 取得user id 為 123 的 user 所寫的所有文章|
| GET/users/123/articles/456| 取得user id 為 123 的 user 所寫的、article id為 456 的文章|
| GET/users/123/videos| 取得user id 為 123 的 user 所錄的所有影片|
| GET/users/100         | 取得user id 為 100 的 user |

3. response body返回json或是xml格式

## 驗證請求參數 - @Valid, @Validated, @NotNull...
1. 使用@RequestBOdy：要在該參數上加上 **@Valid** 註解，才能讓這個class裡的驗證請求參數的註解生效
2. 使用@RequestParam/@RequestHeader/@PathVariable：需要在這個controller上加上 **@Validated** 註解，才能讓驗證請求參數的註解生效

| 註解      | 詳細資訊                                                         |
| --------- | ---------------------------------------------------------------- |
| @NotNull  | 不能為null                                                       |
| @NotBlank | 不能為null且不能為空白的字串，用在驗證String類型的參數上         |
| @NotEmpty | 不能為null且size必須>0，用在驗證集合類型(List, Set, Map)的參數上 |
| @Min(value) | 值必須>=value，用在驗證數字類型的參數上|
| @Max(value) | 值必須<=value，用在驗證數字類型的參數上|

## RestTemplate
* 用途：發起一個REST風格的Http請求
    * 即是可以發起GET, POST, PUT, DELETE的http請求
    * 並且可以將收到的response body中的json字串，轉換成Java Object

## Spring JDBC
NamedParameterJdbcTemplate 可以根據 sql 語法分成兩類：
1. update 方法: INSERT, UPDATE, DELETE
    * update(String sql, Map<String, Object> map)
        * sql: 要執行的SQL語法
        * map: 存放語法中變數的值
```=java
    @Autowired
    private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
    @DeleteMapping("/students/{studentId}")
    public String delete(@PathVariable Integer studentId){
        String sql = "DELETE FROM student WHERE id = :studentId";

        Map<String, Object> map = new HashMap<>();
        map.put("studentId", studentId);

        namedParameterJdbcTemplate.update(sql, map);
        return "執行Delete sql";
    }
```
3. query 方法: SELECT
    * SQL使用*查詢的缺點：
        1. 花費額外的網路流量
        2. 無法提升資料庫查詢的速度
    * query方法永遠會回傳一個List: 取得List中的數據前，記得要先判斷內部是否有數據。
    * RowMapper
        * 用途：將資料庫查詢出來的數據，轉換成Java Object
        * 使用resultSet.getXXX(String column)取得column名字的值
        * 使用resultSet.getXXX(int index)取得第幾順位的column的值
        * resultSet所包含的column，就是SELECT sql中所查出來的那些column
    * ResultSetExtractor
        * 和RowMapper用途一樣，可以組合不同的row之間的數據。  
        
## Controller-Service-Dao三層式架構
1. Controller: 負責接收前端傳來的Http request，並且驗證請求參數
2. Service: 負責業務邏輯
3. Dao: 負責和資料庫溝通
* 注意
    1. Class的命名以Controller, Service, Dao結尾，用來表示這個Class是屬於哪一層。
    2. 將Controller, Service, Dao這些Class變成Bean，並使用@Autowired注入。
    3. Controller不能直接呼叫Dao, Controller只能call Service，再透過Service呼叫Dao
    4. Dao只能去執行SQL, 去存取資料庫內部的數據，不能添加任何業務邏輯。

## Unit Testing
### 目的
* **自動化**測試程式的正確性
### 特性
* 一次只測試一個功能點，一個單元可以是一個method，或是一個API。
* 可以被自動化運作。
* 各個測試間互相獨立，彼此之間沒有依賴關係。
* 測試的結果是穩定的，不受外部服務影響。
### 注意事項
* 為了方便管理，測試程式一定要*放在Test資料夾內*。
* 測試的Class以原class的名字加上Test作為結尾命名。
* 測試Class的Package和原Class的Package的結構一致。
### JUnit 5用法
1. 只要在方法上加上@Test，即可生成一個單元測試。
2. @Test只能在test資料夾下使用，將該方法變成可執行的Test case。
3. 方法名稱可隨意取，用來表達這個test case要測試的功能點，是精華所在。
#### Assert用法
* 或是不符合assert斷言的預期結果，則測試失敗


| Assert系列用法 | 用途 | 
| -------- | -------- | 
| assertNull(A)     | 斷言A為null     | 
| assertNotNull(A)     | 斷言A不為null     | 
| assertEquals(A expected, B)     | 斷言A和B相等，會使用equals()方法判斷     | 
| assertTrue(A)     | 斷言A為true     | 
| assertFalse(A)     | 斷言A為false     | 
| assertThrows(exception, method)     | 斷言執行method時，會噴出exception     | 

#### 其他常用註解
* @BeforeEach, @AfterEach
    在**每次**@Test開始前/結束後，都會執行一次。
* @BeforeAll, @AfterAll(不常用，方法必須為public static void)
    在**所有**@Test開始前/結束後，都會執行一次。 
* @Disabled: 忽略該@Test不執行
* @DisplayName: 自定義顯示名稱

#### 使用 JUnit5 測試Spring Boot程式
只要在測試class上加上@SpringBootTest，在運行單元測試時，SpringBoot就會去啟動Spring容器，創建所有的bean出來(不只是創建bean，所有的@Configuration設定有都會執行，效果等同於運行Spring Boot程式)
* @Transactional
    1. 在test資料夾(單元測試)
        用途：可以加在方法上，也可以加在class上
        用途：在單元測試結束後，rollback所有資料庫操作，將數據恢復原狀。
    2. 在main資料夾(一般測試)
        程式運行中途發生錯誤時，rollback已經執行的資料庫操作，將數據恢復原狀。
#### Controller層測試
1. MockMvc
    用途：模擬真實的API 呼叫
    1. 在測試Class加上@AutoConfigureMockMvc
    2. 注入MockMvc
    ```=java
        @Test
        public void getById() throws Exception {
            // 設定發起的Request與相關參數:
            // 創建HttpRequest, 設定Url路徑，請求參數，header
            RequestBuilder requestBuilder = MockMvcRequestBuilders
                    .get("/students/3");

            // mockMvc.perform發起Http Request
            // .andDo
            // .andExpect()
            // .andReturn 處理Http response, 輸出結果，驗證結果，取得結果
            // jsonPath: https://jsonpath.com/
            mockMvc.perform(requestBuilder)
                    .andDo(print())
                    .andExpect(status().isOk())
                    . (jsonPath("$.id", equalTo(3)))
                    .andExpect(MockMvcResultMatchers.status().is(200));
        }
    ```
2. Mock測試
    * 目的：避免為了測試某一個單元測試，而去建構了整個bean的dependency
    * 作法：創造一個假的bean，去替換掉Spring容器中原有的bean。 
    * Mockito
        * @MockBean: 產生一個假的bean，替換掉Spring容器中的bean，沒有定義的方法，預設返回null。
        1. 模擬方法的返回值
            ```
                Mockito.when().thenReturn();
                Mockito.doReturn().when();
            ```
        2. 模擬拋出exception
            ```
                // 方法不返回void的寫法：
                Mockito.when().thenThrow();
                // 方法返回為void的寫法：
                Mockito.doThrow().when();
            ```
        5. 記錄方法的使用次數、順序
        7. 限制：
            1. 不能mock static方法
            2. 不能mock private方法
            3. 不能mock final class
        4. @SpyBean: 只替換其中幾個方法，Spring中的bean仍是正常的Bean。
3. Run test with coverage:
    * 查看單元測試覆蓋的範圍，但是不要為了提升覆蓋率而寫單元測試，要思考哪些使用場景沒考慮到。
    * 不要被數字疑惑 
4. 如果測試中使用的@SpyBean過多，表示功能切分的不夠好
5. 測試驅動開發(TDD)的流程
## IntelliJ 實用技巧(Mac)
1. 萬用鍵: option + enter
2. 跳轉鍵: cmd + 滑鼠左鍵
3. 全域搜尋: cmd + shift + f
4. 回到前一次游標: cmd + option + 方向鍵左
5. Endpoints: 可找到所有的url-mapping方法
6. 放大視窗：連點視窗兩下
7. 切割分頁：split to right
8. 註解(取消註解)程式：cmd + /
9. 移除多餘的import: ctrl+ option + O
10. 調整排版: cmd + option + l
11. 快速查詢: shift連按兩下
12. 列出近期開過檔案: cmd + e
13. 自動產生: cmd + n
14. 選取多行: option + 按著滑鼠左鍵
15. 同時載入多個spring project: file -> new -> module from existing sources -> 選取pom.xml
## Maven 生命週期
1. clean: 刪除src底下的target資料夾。
2. compile: 編譯spring boot 程式
3. test: 運行單元測試
4. package: 打包成.jar檔，存放在src/target底下
5. install: 將.jar檔存放到Local repository(~/.m2/repository)
6. deploy: 將.jar檔上傳到remote repository中。