會用Magic來形容Spring boot的RESTful,一方面是微服務的架構,另一方面則是透過Spring boot真得很容易完成,本篇開始也就會一些專案的經驗帶進來。
簡單介紹一下REST(Representational State Transfer),具象狀態的傳輸,Wiki是這樣說明的,其實它的出現,主要是因為原本的Web Service太過複雜太謹慎了,SOAP跟XML-RPC不容易讓人一下子就了解,在這惟快不破的時代,時間就是金錢,所以REST的出現,讓原本比較難的Web Service變得簡單易懂,而且也容易上手,有些要點可以參這裡都有說明可以看看,對於微服務來說,無狀態很重要,未來架構設計也應該朝向無狀態,不然很難處理日益暴漲的量,符合REST設計風格的Web API稱為RESTful API,這就是我們常說的RESTful,比Web Service多了幾種操作方式,分別為get、post、put、delete、patch及head等(當然還有其他的),差別在這裡可以參考看看,Web Service有wsdl,那RESTful有嗎?答案就是Swagger,已經可以算是RESTful的標準了,Spring boot整合swagger也是很無縫地喔。
pom.xml 說明
Github code(louisz.springboot.example3)
swagger待續....
參考連結:
https://springframework.guru/spring-boot-restful-api-documentation-with-swagger-2/
http://blog.didispace.com/springbootswagger2/
https://zh.wikipedia.org/wiki/REST
https://data-sci.info/2015/10/24/%E5%B8%B8%E8%A6%8B%E7%9A%84http-method%E7%9A%84%E4%B8%8D%E5%90%8C%E6%80%A7%E8%B3%AA%E5%88%86%E6%9E%90%EF%BC%9Agetpost%E5%92%8C%E5%85%B6%E4%BB%964%E7%A8%AEmethod%E7%9A%84%E5%B7%AE%E5%88%A5/
簡單介紹一下REST(Representational State Transfer),具象狀態的傳輸,Wiki是這樣說明的,其實它的出現,主要是因為原本的Web Service太過複雜太謹慎了,SOAP跟XML-RPC不容易讓人一下子就了解,在這惟快不破的時代,時間就是金錢,所以REST的出現,讓原本比較難的Web Service變得簡單易懂,而且也容易上手,有些要點可以參這裡都有說明可以看看,對於微服務來說,無狀態很重要,未來架構設計也應該朝向無狀態,不然很難處理日益暴漲的量,符合REST設計風格的Web API稱為RESTful API,這就是我們常說的RESTful,比Web Service多了幾種操作方式,分別為get、post、put、delete、patch及head等(當然還有其他的),差別在這裡可以參考看看,Web Service有wsdl,那RESTful有嗎?答案就是Swagger,已經可以算是RESTful的標準了,Spring boot整合swagger也是很無縫地喔。
學習目的:建立一個REST的Controller,並包含了單元測試(Mockito),以及Swagger的設定。
學習時數:2.5 hr
教學影片:pom.xml 說明
spring-boot-starter-web:配置 Web Project所需的函式庫 spring-boot-starter-test:配置 unit or mock test 所需的函式庫 spring-boot-starter-actuator:配置監控spring boot所需的函式庫 springfox-swagger2:配置swagger 2 api所需的函式庫 springfox-swagger-ui:配置swagger ui所需的函式庫程式碼說明
- Example3.java
package louisz.springboot.example3; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; /** * Louisz Spring boot introduce ex.3 * */ @SpringBootApplication public class Example3 { /** * 程式執行起點 * * @param args */ public static void main(String[] args) { SpringApplication.run(Example3.class, args); } }
- Ex3Controller.java
package louisz.springboot.example3; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.BeanUtils; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(value = "/user") // 上層路徑配置 public class Ex3Controller { //單一儲存庫 static Mapusers = Collections.synchronizedMap(new HashMap ()); /** * 取得所有User清單 * * @return */ @RequestMapping(value = "/all", method = RequestMethod.POST) public List getUserList() { // 取得所有User List return new ArrayList (users.values()); } /** * 新增User資料,傳入User JSON資料進行新增,成功回傳success * * @param user * @return */ @RequestMapping(value = "/add", method = RequestMethod.POST) public String addUser(@ModelAttribute User user) { // 傳入Input JSON會自動轉換為User物件 users.put(user.getId(), user); return "add success"; } /** * 傳入id進行查詢,回傳User JSON * * @param id * @return */ @RequestMapping(value = "/find/{id}", method = RequestMethod.POST) public User findUser(@PathVariable Long id) { // Output User會自動轉換為JSON物件 return users.get(id); } /** * 傳入id及JSON User進行資料更新 * * @param id * @param user * @return */ @RequestMapping(value = "/update/{id}", method = RequestMethod.POST) public String updateUser(@PathVariable Long id, @ModelAttribute User user) { // 更新User資料 User u = users.get(id); // System.out.println(u.getName()+"=>"+user.getMobileNumber()); // 使用了BeanUtils BeanUtils.copyProperties(user, u); users.put(id, u); return "update success"; } /** * 傳入id進行資料刪除,回傳success * * @param id * @return */ @RequestMapping(value = "/remove/{id}", method = RequestMethod.DELETE) public String removeUser(@PathVariable Long id) { //刪除user資料 users.remove(id); return "delete success"; } } }
- User.java
package louisz.springboot.example3; public class User { private Long id; private String name; private String email; private String mobileNumber; public Long getId() { return id; } public void setId(Long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getEmail() { return email; } public void setEmail(String email) { this.email = email; } public String getMobileNumber() { return mobileNumber; } public void setMobileNumber(String mobileNumber) { this.mobileNumber = mobileNumber; } }
- SwaggerConfiguration.java
package louisz.springboot.example3; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import springfox.documentation.builders.RequestHandlerSelectors; import springfox.documentation.spi.DocumentationType; import springfox.documentation.spring.web.plugins.Docket; import springfox.documentation.swagger2.annotations.EnableSwagger2; import static springfox.documentation.builders.PathSelectors.regex; @Configuration @EnableSwagger2 public class SwaggerConfiguration { @Bean public Docket usersApi() { return new Docket(DocumentationType.SWAGGER_2).select() .apis(RequestHandlerSelectors.basePackage("louisz.springboot.example3")).paths(regex("/user.*")) .build(); } }
- TestEx3Controller.java(單元測試,請注意註解部分說明)
package louisz.springboot.example3; //這邊要注意import的方式採用static import static org.hamcrest.Matchers.equalTo; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.RequestBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; //spring boot 1.4以後的方式 @RunWith(SpringRunner.class) @WebMvcTest(Ex3Controller.class) public class TestEx3Controller { @Autowired private MockMvc mvc; /** * 單元測試測試前前置設定 * * @throws Exception */ @Before public void setUp() throws Exception { mvc = MockMvcBuilders.standaloneSetup(new Ex3Controller()).build(); } /** * 測試範例三的RestController各項method * * @throws Exception */ @Test public void testEx3Controller() throws Exception { /* * 撰寫單元測試的時候,先想清楚要測試的流程,例如本範例為測試新增修改刪除功能, * 你可以一個一個method測試,你也可以使用先新增一筆,然後進行修改,再查詢後刪 除等,把單元測試想做一個情境會比較好進行撰寫。 */ RequestBuilder request = null; // 我這裡設定使用post進行查詢 // 1.測試是否回覆200的訊息,並且確認是否尚無資料 request = post("/users/all/"); mvc.perform(request).andExpect(status().isOk()).andExpect(content().string(equalTo("[]"))); // 2.新增一筆資料,新增成功後須回覆success的資訊 request = post("/users/add/").param("id", "1").param("name", "Louisz").param("email", "louisz6ster@gmail.com") .param("mobileNumber", "0912345678"); mvc.perform(request).andExpect(content().string(equalTo("success"))); // 3.再進行查詢,因為只有一筆不用特別指定,確認回覆的資訊是否與預期相同 request = post("/users/all/"); mvc.perform(request).andExpect(status().isOk()).andExpect(content().string(equalTo( "[{\"id\":1,\"name\":\"Louisz\",\"email\":\"louisz6ster@gmail.com\",\"mobileNumber\":\"0912345678\"}]"))); // 4.修改原有資訊資料,修改成功後須回覆success的資訊 request = post("/users/update/1").param("name", "Jessie").param("email", "louisz6ster@gmail.com") .param("mobileNumber", "0923456789"); mvc.perform(request).andExpect(content().string(equalTo("success"))); // 5.使用指定查詢進行該資料比對,確認修改的資訊是否正確 request = post("/users/find/1"); mvc.perform(request).andExpect(content().string(equalTo( "{\"id\":1,\"name\":\"Jessie\",\"email\":\"louisz6ster@gmail.com\",\"mobileNumber\":\"0923456789\"}"))); // 6.刪除指定的資料,刪除成功後須回覆success request = delete("/users/remove/1"); mvc.perform(request).andExpect(content().string(equalTo("success"))); // 7.最後再查詢一次全部資料,須回覆空值,表示單元測試完成 request = post("/users/all/"); mvc.perform(request).andExpect(status().isOk()).andExpect(content().string(equalTo("[]"))); } }
先進行單元測試,然後執行程式使用 POSTMAN 測試 測試網址 http://localhost:8090/user Swagger-ui網址 http://localhost:8090/swagger-ui.html Swagger-api網址 http://localhost:8090/v2/api-docs
Github code(louisz.springboot.example3)
swagger待續....
參考連結:
https://springframework.guru/spring-boot-restful-api-documentation-with-swagger-2/
http://blog.didispace.com/springbootswagger2/
https://zh.wikipedia.org/wiki/REST
https://data-sci.info/2015/10/24/%E5%B8%B8%E8%A6%8B%E7%9A%84http-method%E7%9A%84%E4%B8%8D%E5%90%8C%E6%80%A7%E8%B3%AA%E5%88%86%E6%9E%90%EF%BC%9Agetpost%E5%92%8C%E5%85%B6%E4%BB%964%E7%A8%AEmethod%E7%9A%84%E5%B7%AE%E5%88%A5/
留言
張貼留言