跳到主要內容

Spring boot v1.5 (三) magic RESTful and some swagger

    會用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也是很無縫地喔。



學習目的:建立一個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/

留言

這個網誌中的熱門文章

使用Apache Http Server進行Proxy和LoadB alance

環境概述 OS:Linux Apache Http Server:2.2.21 安裝可以使用rpm或是抓source下來compile,建議前者 rpm –ivh apache-http-xxx.rpm 這裡請注意一下安裝的版本 http.conf 參數設定 Proxy < VirtualHost *:80>      ProxyPass targetPath fromPath | fromUrl < VirtualHost/>  範例如下 < VirtualHost *:80>      ProxyPass /app http://DomainName或IP:8888/app      ProxyPassReverse /app http://DomainName或IP:8888/app < VirtualHost/> 上述的參數設定為,將某主機的http通訊協定下的Web application(app),對應到本台Web Server的app下。這樣的Proxy架構的設定對企業內的Web應用程式相當有用處,若是遇到Web Application Server掛點只需要修改Proxy對應,不需要動到dns等等,是個滿便宜的Proxy架構設定喔。﹝不過要注意Web Application要注意redirectc和forward等的撰寫,盡量不要有絕對IP的出現﹞

IBM MQ Server To Server的簡易設定

IBM MQ 的STS建置方式,IBM Red Book已經說明的滿完整的,這裡稍微整理一下,步驟如下 在 MQ-A Server MQ QMGR(MQA) 上需要建立的物件定義如下: • 遠端佇列定義 PAYROLL.QUERY • 傳輸佇列定義 MQB(預設 = 遠端佇列管理程式名稱) • 程序定義 MQA.TO.MQB.PROCESS(對於 WebSphere MQ for AIX、HP-UX、Solaris 與 Windows,及 MQSeries for Compaq Tru64 UNIX 與 OS/2 Warp,並非必要) • 傳送端通道定義 MQA.TO.MQB • 接收端通道定義 MQB.TO.MQA • 本端佇列定義 MQ2 以下是必須建立在 MQ-B Server MQ QMGR(MQB) 中的物件定義: • 遠端佇列定義 PAYROLL.RESPONE • 傳輸佇列定義 MQA(預設值=遠端佇列管理程式名稱) • 程序定義 MQB.TO.MQA.PROCESS(對於 WebSphere MQ for AIX、HP-UX、Solaris 與 Windows,及 MQSeries for Compaq Tru64 UNIX 與 OS/2 Warp,並非必要) • 傳送端通道定義 MQB.TO.MQA • 接收端通道定義 MQA.TO.MQB • 本端佇列定義 MQ4 1.先啟動MQ-A Server上的QMGR(MQA),並使用Runmqsc進入。 2.在佇列管理程式 MQA 上執行下列指令。 遠端佇列定義 DEFINE QREMOTE(PAYROLL.QUERY) DESCR('MQA 的遠端佇列') REPLACE + PUT(ENABLED) XMITQ(MQB) RNAME(MQ4) RQMNAME(MQB) 註: 遠端佇列定義並非實體的佇列,但卻是引導訊息至傳輸佇列 (MQB) 的一種方式,以便能將訊息送至佇列管理程式 MQB。 傳輸佇列定義 DEFINE QLOCAL(MQB) DESCR('對 MQB 的傳輸佇列') REPLACE + USAGE(XMITQ) PUT(ENABLED) GET(ENABLED) TRIGGER TRIGTYPE(FIRST) + INITQ(SYSTEM.CHANN

IReport字型下拉選單中文亂碼

這個問題其實也不是很大啦,不過當你有很多的中文字型檔的時候可能就不知道要選哪一個,啟動IReport後,開啟報表後會發現左邊下拉選單中,最下面的字型清單中有出現方框,顯示不出該字型的名稱,這幾個字型應該是判斷新細明體,標楷體及細明體,如下圖 下載IReport的Source Code來檢查一下,it.businesslogic.ireport.gui.MainFrame發現這個JComboBox有特別設定Arial字型,當然只要是中文的都顯示不出來ㄚ,所以點掉這一行後重新編譯,嘿嘿就可以了。 jComboBoxFont.setFont(new java.awt.Font("Arial", 0, 11)); 我目前使用的版本為 IReport-3.0.0-src