一、引數管理
在程式設計系統中,為了能寫出良好的程式碼,會根據是各種設計模式、原則、約束等去規範程式碼,從而提高程式碼的可讀性、複用性、可修改,實際上個人覺得,如果寫出的程式碼很好,即別人修改也無法破壞原作者的思路和封裝,這應該是非常高水準。
但是在日常開發中,礙於很多客觀因素,很少有時間去不斷思考和優化程式碼,所以只能從實際情況的角度去思考如何構建系統程式碼,保證以後自己還能讀懂自己的程式碼,在自己的幾年程式設計中,實際會考慮如下幾個方面:程式碼層級管理,命名和註釋統一,合理的設計業務資料庫,明確引數風格。
這裡就來聊一下引數管理,圍繞:入參、校驗、返參三個方面內容。
如何理解程式碼規範這個概念:即大多數開發認同,願意遵守的約束,例如Spring框架和Mvc模式對於工程的管理,《Java開發手冊》中對於業務開發的規定,其根本目的都是想避免隨著業務發展,程式碼演變到無法維護的境界。
二、接收引數
接收引數方式有很多種,List,Map,Object等等,但是為了明確引數的語義,通常都需要設計引數物件的結構並且遵守一定的規範,例如明確禁止Map接收引數:
Rest風格接收單個ID引數:
@GetMapping("/param/single/{id}")
public String paramSingle (@PathVariable Integer id){
return "Resp:"+id ;
}
接收多個指定的引數:
@GetMapping("/param/multi")
public String paramMulti (@RequestParam("key") String key, @RequestParam("var") String var){
return "Resp:"+key+var ;
}
基於Java包裝物件入參:
@PostMapping("/param/wrap")
public ParamIn paramWrap (@RequestBody ParamIn paramIn){
return paramIn ;
}
-- 引數物件實體
public class ParamIn {
private Integer id ;
private String key ;
private String var ;
private String name ;
}
以上是在開發中常用的幾種接參方式,這裡通常會遵守下面幾個習慣:
- 引數語義:明確接收引數的作用;
- 個數限制:引數超過三個使用包裝物件;
- 避免多個介面使用單個包裝物件入參;
- 避免包裝物件主體過於複雜;
引數接收並沒有很複雜的約束,整體上也比較容易遵守,通常的問題在於處理較大主體物件時,容易產生一個包裝物件被多處複用,進而導致物件欄位屬性很多,這種情況在複雜業務中尤其容易出現,這種物件並不利於web層介面使用,或者很多時候都會在業務層和介面層混用物件;
在業務層封裝複雜的BO物件來降低業務管理的複雜度,這是合理常見的操作,可以在web介面層面根據介面功能各自管理入參主體,在業務實現的過程中,再傳入BO物件中。
避免複雜的業務包裝物件在各個層亂飄,如果多個介面入參都是同一個複雜的物件,很容易讓開發人員迷茫。
三、響應引數
與引數接收相對應的就是引數響應,引數響應通常具有明確的約束規範:響應主體資料,響應碼,描述資訊。通常來說就是這樣三個核心要素。
響應引數主體:
這裡泛型的使用通常用來做主體資料的接收。
public class Resp<T> {
private int code ;
private String msg ;
private T data ;
public static <T> Resp<T> ok (T data) {
Resp<T> result = new Resp<>(HttpStatus.OK);
result.setData(data);
return result ;
}
public Resp (HttpStatus httpStatus) {
this.code = httpStatus.value();
this.msg = httpStatus.getReasonPhrase();
}
public Resp(int code, String msg, T data) {
this.code = code;
this.msg = msg;
this.data = data;
}
}
Code狀態碼
即介面狀態,建議參照並遵守HttpStatus
中狀態碼的描述,這是開發普遍遵守的規範,如果不滿足業務需求,在適當自定義部分編碼,可以完全自定義一套響應碼,但是沒太多必要。
Msg描述
描述介面的響應的Msg可能就是:成功或失敗,更多的時候是需要處理業務異常的提示資訊,例如單號不存在,賬號凍結等等,通常需要從業務異常中捕獲提示資訊,並響應頁面,或者入參校驗不通過的描述。
Data資料
介面響應的主體資料,不同的業務響應的物件肯定不同,所以這裡基於泛型機制接收即可,再以JSON格式響應頁面。
參考案例
介面返參:
@PostMapping("/resp/wrap")
public Resp<KeyValue> respWrap (@RequestBody KeyValue keyValue){
return Resp.ok(keyValue) ;
}
響應格式:
{
"code": 200,
"msg": "OK",
"data": {
"key": "hello",
"value": "world"
}
}
四、引數校驗
引數接收和響應相對都不是複雜的,比較難處理的就是引數校驗:入參約束校驗,業務合法性校驗,響應引數非空非null校驗,等各種場景。
在系統執行過程中,任何引數都不是絕對可靠的,所以引數校驗隨處可見,不同場景下的引數校驗,都有其必要性,但其根本目的都是為了給到請求端提示資訊,快速打斷流程,快速響應。
1、借鑑參考
很多封裝思想,設計模式,或者這裡說的引數校驗,都可以參考現有Java原始碼或者優秀的框架,這是一個應該具備的基礎意識。
Java原生方法之java.lang.Thread
執行緒:
public void interrupt() {
if (this != Thread.currentThread())
checkAccess();
synchronized (blockerLock) {
Interruptible b = blocker;
if (b != null) {
interrupt0();
b.interrupt(this);
return;
}
}
interrupt0();
}
在Java原始碼中,大部分都是採用原生的if判斷方式,對引數執行校驗
Spring框架之org.springframework.util.ClassUtils
工具類部分程式碼:
public static Class<?> forName(String name, @Nullable ClassLoader classLoader)
throws ClassNotFoundException, LinkageError {
Assert.notNull(name, "Name must not be null");
Class<?> clazz = resolvePrimitiveClassName(name);
if (clazz == null) {
clazz = commonClassCache.get(name);
}
if (clazz != null) {
return clazz;
}
}
在Spring框架中除了基礎的if判斷之外,還封裝一個org.springframework.util.Assert
斷言工具類。
2、常用校驗方式
If判斷
@GetMapping("/check/base")
public String baseCheck (@RequestParam("var") String var){
if (var == null) {
return var+" is null" ;
}
if ("".equals(var)){
return var+" is empty" ;
}
if ("hello".equals(var)){
return var+" sensitive word " ;
}
return var + " through " ;
}
這種判斷在程式碼中很常見,只是一旦遇到校驗的主體物件很大,並且在分散式的環境中,需要重複寫if判斷的話,容易出錯是一個方面,對開發人員的耐心考驗是另一個方面。
Valid元件
在早幾年的時候,比較流行的常用校驗元件Hibernate-Validator
,後來興起的Validation-Api
,據說是參考前者實現,不過這並不重要,二者都簡化了對JavaBean的校驗機制。
基於註解的方式,標記Java物件的欄位屬性,並設定如果校驗失敗的提示資訊。
public class JavaValid {
@NotNull(message="ID不能為空")
private Integer id ;
@Email(message="郵箱格式異常")
private String email ;
@NotEmpty(message = "欄位不能為空")
@Size(min = 2,max = 10,message = "欄位長度不合理")
private String data ;
}
校驗結果列印:
public class JavaValidTest {
private static Validator validator ;
@BeforeClass
public static void beforeBuild (){
validator = Validation.buildDefaultValidatorFactory().getValidator();
}
@Test
public void checkValid (){
JavaValid valid = new JavaValid(null,"email","data") ;
Set<ConstraintViolation<JavaValid>> validateInfo = validator.validate(valid) ;
// 列印校驗結果
validateInfo.stream().forEach(validObj -> {
System.out.println("validateInfo:"+validObj.getMessage());
});
}
}
介面使用:
@PostMapping("/java/valid")
public JavaValid javaValid (@RequestBody @Valid JavaValid javaValid,BindingResult errorMsg){
if (errorMsg.hasErrors()){
List<ObjectError> objectErrors = errorMsg.getAllErrors() ;
objectErrors.stream().forEach(objectError -> {
logger.info("CheckRes:{}",objectError.getDefaultMessage());
});
}
return javaValid ;
}
這種校驗機制基於註解方式,可以大幅度簡化普通的入參校驗,但是對業務引數的合法校驗並不適應,例如常見的ID不存在,狀態攔截等。
Assert斷言
關於Assert斷言方式,起初是在單元測試中常見,後來在各種優秀的框架中開始常見,例如Spring、Mybatis等,然後就開始出現在業務程式碼中:
public class AssertTest {
private String varObject ;
@Before
public void before (){
varObject = RandomUtil.randomString(6) ;
}
@Test
public void testEquals (){
Assert.assertEquals(varObject+"不匹配",varObject,RandomUtil.randomString(6));
}
@Test
public void testEmpty (){
Assert.assertTrue(StrUtil.isNotEmpty(varObject));
Assert.assertFalse(varObject+" not empty",StrUtil.isNotEmpty(varObject));
}
@Test
public void testArray (){
/*
陣列元素不相等: arrays first differed at element [1];
Expected :u08
Actual :mwm
*/
String var = RandomUtil.randomString(5) ;
String[] arrOne = new String[]{var,RandomUtil.randomString(3)} ;
String[] arrTwo = new String[]{var,RandomUtil.randomString(3)} ;
Assert.assertArrayEquals("陣列元素不相等",arrOne,arrTwo);
}
}
Assert斷言,可以替換傳統的if判斷,大量減少引數校驗的程式碼行數,提高程式的可讀性,這種風格是目前比較流行的方式。
五、原始碼地址
GitHub·地址
https://github.com/cicadasmile/middle-ware-parent
GitEE·地址
https://gitee.com/cicadasmile/middle-ware-parent
閱讀標籤
【Java基礎】【設計模式】【結構與演算法】【Linux系統】【資料庫】
【分散式架構】【微服務】【大資料元件】【SpringBoot進階】【Spring&Boot基礎】