SpringBoot項(xiàng)目實(shí)戰(zhàn)之?dāng)?shù)據(jù)交互篇
前言
SpringBoot 非常適合 Web 應(yīng)用開(kāi)發(fā),我們可以使用它輕松地建立一個(gè) Web 服務(wù)。在Spring Boot入門(mén) 里面,我們已經(jīng)使用其實(shí)現(xiàn)一個(gè)非常簡(jiǎn)單的接口,輸出了 Hello World!下面我們模擬真實(shí)的場(chǎng)景來(lái)學(xué)習(xí) SpringBoot 應(yīng)用開(kāi)發(fā)。
1. 數(shù)據(jù)格式
在實(shí)際的項(xiàng)目場(chǎng)景中,前后分離幾乎是所以項(xiàng)目的標(biāo)配,全棧的時(shí)代的逐漸遠(yuǎn)去,后端負(fù)責(zé)業(yè)務(wù)邏輯處理,前端負(fù)責(zé)數(shù)據(jù)展示成了一種固定的開(kāi)發(fā)模式。一般來(lái)說(shuō),在后端提供的數(shù)據(jù)接口中,可能會(huì)存在兩種數(shù)據(jù)形式 Json 與 XML,具體會(huì)用到哪一種,往往會(huì)與公司的工程師文化相關(guān)。
1.1. Json 報(bào)文
對(duì)于 SpringBoot 來(lái)說(shuō) ,它默認(rèn)會(huì)使用 Json 作為響應(yīng)報(bào)文格式,我們用一個(gè)簡(jiǎn)單的例子做一下測(cè)試:
首先,我們創(chuàng)建一個(gè) UserController 用于處理前端的 Web 請(qǐng)求。
@Controller @RequestMapping("/sys/user") public class UserController { @RequestMapping("login") @ResponseBody public Map<String, String> login() { Map<String, String> hashMap = new HashMap<>(); hashMap.put("msg", "登錄成功"); return hashMap; } }
相信這個(gè)事例對(duì)于大多數(shù)使用過(guò) SpringMVC 的人來(lái)說(shuō)都不會(huì)陌生,定義一個(gè)簡(jiǎn)單的控制器及相當(dāng)?shù)挠成?,與通常返回 Url 的 Controller 不一樣的是,我們這里使用了 @ResponseBody 注解,它表示此接口響應(yīng)為純數(shù)據(jù),不帶任何界面展示。請(qǐng)求對(duì)應(yīng)的地址看到如下輸出:
很明顯,我們得到了想要的結(jié)果,一個(gè)標(biāo)準(zhǔn)格式的 Json 字符串,是不是非常的簡(jiǎn)單。
對(duì)于上面的代碼來(lái)說(shuō),還可以做進(jìn)一步的優(yōu)化,由于所有的 Restful 接口都只是返回?cái)?shù)據(jù),所以我們可以直接在類(lèi)級(jí)別上添加 @ResponseBody 注解。而大多數(shù)情況下,@Controller 與 @ResponseBody 又會(huì)一起使用,所以我們使用 @RestController 注解來(lái)替換掉它們,從而更加簡(jiǎn)潔地實(shí)現(xiàn)功能。
@RestController @RequestMapping("/sys/user") public class UserController { @RequestMapping("login") public Map<String, String> login() { Map<String, String> hashMap = new HashMap<>(); hashMap.put("msg", "登錄成功"); return hashMap; } }
1.2. Xml 報(bào)文
大多數(shù)情況下,使用 Json 就可以滿(mǎn)足我們的需求了,但仍然存在某些特定的場(chǎng)景需要使用到 XML 形式的報(bào)文,如:微信公眾號(hào)開(kāi)發(fā)。不過(guò)不用擔(dān)心,切換成 XML 報(bào)文也只需要做輕微的改動(dòng),添加相關(guān)依賴(lài)如下:
"com.fasterxml.jackson.dataformat:jackson-dataformat-xml:2.8.8"
然后就可以開(kāi)始進(jìn)行測(cè)試了,此處借助一個(gè)模擬 HTTP 請(qǐng)求工具(Postman)來(lái)協(xié)助我們測(cè)試該接口,使用方法就不多說(shuō)了,直接上圖:
在上面的測(cè)試范例里,我們指定了 Accept 為 text/xml,這樣 SpringBoot 就會(huì)返回 XML 形式的數(shù)據(jù)。
2. 接口規(guī)范
對(duì)于每一家公司來(lái)說(shuō),都會(huì)定義自己的數(shù)據(jù)規(guī)范,一個(gè)統(tǒng)一且標(biāo)準(zhǔn)的數(shù)據(jù)規(guī)范對(duì)于系統(tǒng)維護(hù)來(lái)說(shuō)是非常重要的,也在很在程度上提升了開(kāi)發(fā)效率。
2.1. 響應(yīng)報(bào)文規(guī)范
一般來(lái)說(shuō),接口響應(yīng)至少需要告訴使用方三項(xiàng)信息:狀態(tài)碼、描述、數(shù)據(jù)。其中,數(shù)據(jù)不是每個(gè)接口必須的,如果只是一個(gè)簡(jiǎn)單修改的動(dòng)作,可能就沒(méi)有必須返回?cái)?shù)據(jù)了。下面我們定義一個(gè) Result 類(lèi)來(lái)封裝我們的響應(yīng)報(bào)文。
public class Result { private int code; private String msg; private Object data; public Result(ResultCode resultCode, Object data) { this(resultCode); this.data = data; } public Result(ResultCode resultCode) { this.code = resultCode.getCode(); this.msg = resultCode.getMsg(); } ... }
同時(shí),定義一個(gè)枚舉類(lèi)來(lái)維護(hù)我們的狀態(tài)碼:
public enum ResultCode { SUCCESS(0, "請(qǐng)求成功"), WARN(-1, "網(wǎng)絡(luò)異常,請(qǐng)稍后重試"); private int code; private String msg; ResultCode(int code, String msg) { this.msg = msg; } public int getCode() { return code; } public String getMsg() { return msg; } }
這樣,我們的響應(yīng)數(shù)據(jù)規(guī)范已基本建立。將上面代碼中返回 Map 修改為返回 Result,按照指定規(guī)范返回?cái)?shù)據(jù),得到結(jié)果如下。
2.2. 請(qǐng)求數(shù)據(jù)規(guī)范
響應(yīng)報(bào)文格式我們已經(jīng)定義好了,那么請(qǐng)求數(shù)據(jù)我們?nèi)绾谓邮漳??一般?lái)說(shuō),請(qǐng)求與響應(yīng)會(huì)使用相同的報(bào)文形式,即,如果響應(yīng)為Json,那么請(qǐng)求也建議使用Json。如果想要為上面的登錄請(qǐng)求添加輸入?yún)?shù),需要完成以下幾步:
首先,我們定義好用戶(hù)實(shí)體:
public class User { private String username; private String password; ... }
然后,直接在映射方法里面使用該實(shí)體進(jìn)行參數(shù)接收,并將接收到的參數(shù)直接返回:
@RestController @RequestMapping("/sys/user") public class UserController { @RequestMapping("login") public Result login(@RequestBody User loginUser) { return new Result(ResultCode.SUCCESS, loginUser); } }
調(diào)出我們的Postman,填寫(xiě)正確的Url,選擇POST方式發(fā)送請(qǐng)求,選擇Body,將 Content-Type 設(shè)置成 application/json,填入 Json 格式的請(qǐng)求數(shù)據(jù),點(diǎn)擊 Send 即可得到如下結(jié)果。
數(shù)據(jù)接收非常成功,但在上面的響應(yīng)報(bào)文中,存在著了一個(gè)非常嚴(yán)重的問(wèn)題,那就是用戶(hù)的密碼也隨同用戶(hù)信息一起返回給了客戶(hù)端,顯然這并不是一種正確的做法。我們需要對(duì)其進(jìn)行一次過(guò)濾,由于 SpringBoot 默認(rèn)使用 Jackson 作為 Json 序列化工具,如果想要過(guò)濾掉響應(yīng)中的某些字段,只需在過(guò)濾字段對(duì)應(yīng)的 get 方法上加上 @JsonIgnore 注解即可。但這樣又會(huì)引發(fā)另外一個(gè)問(wèn)題,那就是請(qǐng)求中的字段也被過(guò)濾掉了,對(duì)于這種問(wèn)題,可以采用抽離請(qǐng)求參數(shù)模型的方式進(jìn)行處理,即自定義一套參數(shù)接收的 Model,比如,接收用戶(hù)登錄的會(huì)使用 UserModel 來(lái)進(jìn)行參數(shù)接收,這樣使得請(qǐng)求參數(shù)模型與數(shù)據(jù)庫(kù)映射實(shí)體完全分離,在一定程度上提升了系統(tǒng)的安全性。
@RequestMapping("login") public Result login(@RequestBody UserModel userModel) { User user = new User(); user.setUsername(userModel.getUsername()); user.setPassword(userModel.getPassword()); return new Result(ResultCode.SUCCESS, user); }
替換成 Model 對(duì)象后,我們就可以在數(shù)據(jù)庫(kù)映射實(shí)體 User 上增加 @JsonIgnore 注解忽略該字段的序列化,而不影響請(qǐng)求參數(shù)的輸入。
3. 參數(shù)校驗(yàn)
出于系統(tǒng)健壯性的考慮,一般來(lái)說(shuō),我們需要對(duì)所有的參數(shù)進(jìn)行必要性校驗(yàn),如:登錄請(qǐng)求時(shí),如果沒(méi)有用戶(hù)名,程序應(yīng)該立即駁回該請(qǐng)求。上面請(qǐng)求參數(shù)模型(Model)的抽象也使得我們對(duì)數(shù)據(jù)校驗(yàn)更加方便,當(dāng)然主要還是依賴(lài)于 SpringBoot 的 Validate 功能的強(qiáng)大支持。
3.1. 簡(jiǎn)單參數(shù)校驗(yàn)
對(duì)于登錄接口來(lái)說(shuō),用戶(hù)名與密碼都是必輸?shù)?,那么我們現(xiàn)在為其添加上對(duì)應(yīng)的參數(shù)校驗(yàn),無(wú)需 if-else 判斷,簡(jiǎn)單的幾個(gè)注解就可以幫助我們完成所有的工作。
@RequestMapping("login") public Result login(@RequestBody @Valid UserModel userModel) { ... } --- public class UserModel { private String username; private String password; @NotBlank(message = "用戶(hù)名不能為空") public String getUsername() { return username; } @NotBlank(message = "密碼不能為空") public String getPassword() { return password; } ... }
在上面的示例中,我們?yōu)檎?qǐng)求參數(shù)的 Model 對(duì)象加上了 @Valid 注解,并在 Model 類(lèi)中,對(duì)需要校驗(yàn)字段的 get 方法上添加相應(yīng)的校驗(yàn)注解。效果如下:
3.2. 復(fù)雜參數(shù)校驗(yàn)
3.2.1. 正則表達(dá)式校驗(yàn)
如果用戶(hù)的登錄名為手機(jī)號(hào),那么就需要對(duì)登錄名的格式做進(jìn)一步的校驗(yàn),下面使用正則表達(dá)式來(lái)校驗(yàn)手機(jī)號(hào)的合法性。
@NotBlank(message = "用戶(hù)名不能為空") @Pattern( regexp = "1(([38]\\d)|(5[^4&&\\d])|(4[579])|(7[0135678]))\\d{8}", message = "手機(jī)號(hào)格式不合法" ) public String getUsername() { return username; }
3.2.2. 自定義校驗(yàn)注解
在系統(tǒng)使用過(guò)程中,有很多地方需要對(duì)手機(jī)號(hào)的格式進(jìn)行校驗(yàn),如:注冊(cè)、驗(yàn)證碼發(fā)送等。但校驗(yàn)手機(jī)號(hào)的正則表達(dá)式又過(guò)于復(fù)雜,如果多處編寫(xiě),一旦運(yùn)營(yíng)商增加某個(gè)號(hào)段,對(duì)程序的維護(hù)人員來(lái)說(shuō)就是一個(gè)噩耗。這時(shí),可以使用自定義校驗(yàn)注解來(lái)代替這些常用的校驗(yàn)。
手機(jī)號(hào)校驗(yàn)實(shí)現(xiàn)類(lèi) 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(); } }
手機(jī)號(hào)校驗(yàn)注解 Phone:
@Constraint(validatedBy = PhoneValidator.class) @Target({ElementType.METHOD, ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Phone { String message() default "手機(jī)號(hào)格式不合法"; Class<?>[] groups() default {}; Class<? extends Payload>[] payload() default {}; }
Model 上的使用:
@Phone public String getUsername() { return username; }
這樣的話,如果因?yàn)槟承┎豢煽咕芤蛩貙?dǎo)致校驗(yàn)規(guī)則的變動(dòng),只需要修改一處理即可,維護(hù)成本大大降低。
項(xiàng)目的 github 地址:https://github.com/qchery/funda
總結(jié)
到此這篇關(guān)于SpringBoot項(xiàng)目實(shí)戰(zhàn)之?dāng)?shù)據(jù)交互篇的文章就介紹到這了,更多相關(guān)SpringBoot數(shù)據(jù)交互內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
如何基于Java實(shí)現(xiàn)對(duì)象List排序
這篇文章主要介紹了如何基于Java實(shí)現(xiàn)對(duì)象List排序,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-01-01Maven項(xiàng)目打包成war包部署到Tomcat的方法
這篇文章主要介紹了Maven項(xiàng)目打包成war包部署到Tomcat的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06Mybatis中傳遞多個(gè)參數(shù)的4種方法總結(jié)
這篇文章主要給大家介紹了關(guān)于Mybatis中傳遞多個(gè)參數(shù)的4種方法,并且介紹了關(guān)于使用Mapper接口時(shí)參數(shù)傳遞方式,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-04-04SpringBoot整合Web開(kāi)發(fā)之文件上傳與@ControllerAdvice
@ControllerAdvice注解是Spring3.2中新增的注解,學(xué)名是Controller增強(qiáng)器,作用是給Controller控制器添加統(tǒng)一的操作或處理。對(duì)于@ControllerAdvice,我們比較熟知的用法是結(jié)合@ExceptionHandler用于全局異常的處理,但其作用不止于此2022-08-08Spring?boot?整合RabbitMQ實(shí)現(xiàn)通過(guò)RabbitMQ進(jìn)行項(xiàng)目的連接
RabbitMQ是一個(gè)開(kāi)源的AMQP實(shí)現(xiàn),服務(wù)器端用Erlang語(yǔ)言編寫(xiě),支持多種客戶(hù)端,這篇文章主要介紹了Spring?boot?整合RabbitMQ實(shí)現(xiàn)通過(guò)RabbitMQ進(jìn)行項(xiàng)目的連接,需要的朋友可以參考下2022-10-10使用SpringBoot實(shí)現(xiàn)微服務(wù)超時(shí)重試模式的示例
這篇文章主要介紹了使用SpringBoot實(shí)現(xiàn)微服務(wù)超時(shí)重試模式的示例,幫助大家更好的理解和使用springboot框架,感興趣的朋友可以了解下2020-11-11通過(guò)簡(jiǎn)易例子講解Java回調(diào)機(jī)制
這篇文章主要介紹了通過(guò)簡(jiǎn)易例子講解Java回調(diào)機(jī)制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-11-11Java使用Scanner類(lèi)進(jìn)行文件的讀取方式
這篇文章主要介紹了Java使用Scanner類(lèi)進(jìn)行文件的讀取方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。2022-11-11Spring中使用事務(wù)嵌套時(shí)需要警惕的問(wèn)題分享
最近項(xiàng)目上有一個(gè)使用事務(wù)相對(duì)復(fù)雜的業(yè)務(wù)場(chǎng)景報(bào)錯(cuò)了。在絕大多數(shù)情況下,都是風(fēng)平浪靜,沒(méi)有問(wèn)題。其實(shí)內(nèi)在暗流涌動(dòng),在有些異常情況下就會(huì)報(bào)錯(cuò),這種偶然性的問(wèn)題很有可能就會(huì)在暴露到生產(chǎn)上造成事故,那究竟是怎么回事呢?本文就來(lái)簡(jiǎn)單講講2023-04-04