1. 程式人生 > >SpringBoot學習之Json資料互動

SpringBoot學習之Json資料互動

最近在弄監控主機專案,對javaweb又再努力學習。實際的專案場景中,前後分離幾乎是所以專案的標配,全棧的時代的逐漸遠去,後端負責業務邏輯處理,前端負責資料展示成了一種固定的開發模式。像thymeleaf這種東西沒法實現前後端分離模板難學也只有寫java的才用吧,還是用js模板引擎接受json好。

1. Json報文

SpringBoot 預設會使用 Json 作為響應報文格式。首先,我們建立一個 UserController 用於處理前端的 Web 請求。
定義一個簡單的控制器,與通常返回 Url 的 Controller 不一樣的是,login() 使用了 @ResponseBody 註解,它表示此介面響應為純資料,不帶任何介面展示,可以獲得標準Json

@Controller
@RequestMapping("/user")
public class UserController {

    @RequestMapping("/login")
    @ResponseBody
    public RespEntity login(@RequestBody ReqUser reqUser) {    //使用reqUser模型來接受,而不用User

        User user = new User();
        if(reqUser != null) {
            user.setName(reqUser.getName());
            user.setPassword(reqUser.getPassword());
        }

        return new RespEntity(RespCode.SUCCESS, user);    //返回的響應實體具體看下節
    }
}

對於上面的程式碼來說,還可以做進一步的優化,由於所有的 Restful 介面都只是返回資料,所以我們可以直接在類級別上新增 @ResponseBody 註解。而大多數情況下,@Controller 與 @ResponseBody 又會一起使用,所以我們使用 @RestController 註解來替換掉它們,從而更加簡潔地實現功能。

2. 介面規範

對於每一家公司來說,都會定義自己的資料規範,一個統一且標準的資料規範對於系統維護來說是非常重要的,也在很在程度上提升了開發效率。

2.1 響應報文規範

介面響應至少需要告訴使用方三項資訊:狀態碼、描述、資料。其中,資料不是每個介面必須的,如果只是一個簡單修改的動作,可能就沒有必須返回資料了。下面我們定義一個 RespEntity類來封裝我們的響應報文model:

public class RespEntity {
    private int code;
    private String msg;
    private Object data;

    public RespEntity(RespCode respCode) {
        this.code = respCode.getCode();
        this.msg = respCode.getMsg();
    }

    public RespEntity(RespCode respCode, Object data) {
        this(respCode);
        this.data = data;
    }

    ...    
}

同時,定義一個列舉類來維護我們的狀態碼:

public enum RespCode {

    SUCCESS(0, "請求成功"),
    WARN(-1, "網路異常,請稍後重試");

    private int code;
    private String msg;

    RespCode(int code, String msg) {
        this.msg = msg;
    }

    public int getCode() {
        return code;
    }
    public String getMsg() {
        return msg;
    }
}

這樣,我們的響應資料規範已基本建立。

2.2 請求資料規範

響應報文格式我們已經定義好了,那麼請求資料我們如何接收呢?
一般來說,請求與響應會使用相同的報文形式。如果響應為Json,那麼請求也建議使用Json。
為登入請求新增輸入引數,首先,需要我們定義好使用者實體User類,直接在對映方法login() 使用該實體進行引數接收,並將接收到的引數直接返回,1.節程式碼已實現。
調出Postman,填寫正確的Url,選擇POST方式傳送請求,選擇Body,將 Content-Type 設定成 application/json,填入 Json 格式的請求資料,點選 Send 即可得到如下結果。
659358-20171016211638302-1332469219.png
資料接收非常成功,但在上面的響應報文中,存在著了一個非常嚴重的問題,那就是使用者的密碼也隨同使用者資訊一起返回給了客戶端,顯然這並不是一種正確的做法。
我們需要對其進行一次過濾,由於 SpringBoot 預設使用 Jackson 作為 Json 序列化工具,如果想要過濾掉響應中的某些欄位,只需在過濾欄位對應的 get 方法上加上 @JsonIgnore 註解即可。
但這樣又會引發另外一個問題,那就是請求中的欄位也被過濾掉了,對於這種問題,可以採用抽離請求引數模型的方式進行處理,即自定義一套引數接收的 Model,比如,接收使用者登入的會使用 ReqUser 來進行引數接收,這樣使得請求引數模型與資料庫對映實體完全分離,在一定程度上提升了系統的安全性。替換成 Model 物件後(1.節的程式碼已經替換好了),我們就可以在資料庫對映實體 User 上增加 @JsonIgnore 註解忽略該欄位的序列化,而不影響請求引數的輸入。
659358-20171016211659115-1870302011.png

3. 引數校驗

出於系統健壯性的考慮,我們需要對所有的引數進行必要性校驗,如:登入請求時,如果沒有使用者名稱,程式應該立即駁回該請求。上面請求引數模型(Model)的抽象也使得我們對資料校驗更加方便,當然主要還是依賴於 SpringBoot 的 Validate 功能的強大支援。

3.1. 簡單引數校驗

對於登入介面來說,使用者名稱與密碼都是必輸的,那麼我們現在為其新增上對應的引數校驗,無需 if-else 判斷,簡單的幾個註解就可以幫助我們完成所有的工作。

public class LoginController {

    @RequestMapping("/login")
    @ResponseBody
    public RespEntity login(@RequestBody @Valid ReqUser reqUser) {

    }
}
----
public class ReqUser {
    @NotBlank(message = "使用者名稱不能為空")
    public String getName() {
        return name;
    }

    @NotBlank(message = "密碼不能為空")
    public String getPassword() {
        return password;
    }
    ...
}

我們為請求引數的 Model 物件ReqUser 加上了 @Valid 註解,並在 Model 類中對需要校驗欄位的 get 方法上新增相應的校驗註解。效果如下:
659358-20171016211715568-599613899.png

3.2. 複雜引數校驗

  • 正則表示式校驗
    如果使用者的登入名為手機號,那麼就需要對登入名的格式做進一步的校驗,下面使用正則表示式來校驗手機號的合法性。
@NotBlank(message = "使用者名稱不能為空")
@Pattern(
        regexp = "1(([38]\\d)|(5[^4&&\\d])|(4[579])|(7[0135678]))\\d{8}",
        message = "手機號格式不合法"
)
public String getUsername() {
    return username;
}
  • 自定義校驗註解
    在系統使用過程中,有很多地方需要對手機號的格式進行校驗,如:註冊、驗證碼傳送等。
    但校驗手機號的正則表示式又過於複雜,如果多處編寫,一旦運營商增加某個號段,對程式的維護人員來說就是一個噩耗。這時,可以使用自定義校驗註解來代替這些常用的校驗。

手機號校驗註解 Phone:

@Constraint(validatedBy = PhoneValidator.class)
@Target({ElementType.METHOD, ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Phone {

    String message() default "手機號格式不合法";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

手機號校驗實現類 PhoneValidator:

public class PhoneValidator implements ConstraintValidator<Phone, String> {

    private Pattern pattern = Pattern.compile("1(([38]\\d)|(5[^4&&\\d])|(4[579])|(7[0135678]))\\d{8}");

    @Override
    public void initialize(Phone phone) {
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        return pattern.matcher(value).matches();
    }
}

Model 上的使用:

@Phone
public String getUsername() {
    return username;
}

這樣的話,如果因為某些不可抗拒因素導致校驗規則的變動,只需要修改一處理即可,維護成本大大降低。
659358-20171016211735318-1245660273.png

4. Xml 報文

大多數情況下,使用 Json 就可以滿足我們的需求了,但仍然存在某些特定的場景需要使用到 XML 形式的報文,如:微信公眾號開發。不過不用擔心,切換成 XML 報文也只需要做輕微的改動,新增相關依賴如下:"com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.8.8"
然後就可以開始進行測試了,此處藉助一個模擬 HTTP 請求工具(Postman)來協助我們測試該介面:
659358-20171016211749459-314907000.png
在上面的測試範例裡,我們指定了 Accept 為 text/xml,這樣 SpringBoot 就會返回 XML 形式的資料。