# 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中。