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