會用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 Map users = 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/
留言
張貼留言