溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

Springboot如何使用@Valid 和AOP做參數(shù)校驗(yàn)及日志輸出問(wèn)題

發(fā)布時(shí)間:2021-07-30 14:04:03 來(lái)源:億速云 閱讀:196 作者:小新 欄目:編程語(yǔ)言

這篇文章將為大家詳細(xì)講解有關(guān)Springboot如何使用@Valid 和AOP做參數(shù)校驗(yàn)及日志輸出問(wèn)題,小編覺(jué)得挺實(shí)用的,因此分享給大家做個(gè)參考,希望大家閱讀完這篇文章后可以有所收獲。

項(xiàng)目背景

最近在項(xiàng)目上對(duì)接前端的的時(shí)候遇到了幾個(gè)問(wèn)題

1.經(jīng)常要問(wèn)前端要請(qǐng)求參數(shù)

2.要根據(jù)請(qǐng)求參數(shù)寫(xiě)大量if...else,代碼散步在 Controller 中,影響代碼質(zhì)量

3.為了解決問(wèn)題1,到處記日志,導(dǎo)致到處改代碼

解決方案

為了解決這類問(wèn)題,我使用了@Valid 做參數(shù)校驗(yàn),并使用AOP記錄前端請(qǐng)求日志

1.Bean實(shí)體類增加注解

對(duì)要校驗(yàn)的實(shí)體類增加注解,如果實(shí)體類中有List結(jié)構(gòu),就在List上加@Valid

@Valid注解

注解備注
@Null只能為null
@NotNull必須不為null
@Max(value)必須為一個(gè)不大于 value 的數(shù)字
@Min(value)必須為一個(gè)不小于 value 的數(shù)字
@AssertFalse必須為false
@AssertTrue必須為true
@DecimalMax(value)必須為一個(gè)小于等于 value 的數(shù)字
@DecimalMin(value)必須為一個(gè)大于等于 value 的數(shù)字
@Digits(integer,fraction)必須為一個(gè)小數(shù),且整數(shù)部分的位數(shù)不能超過(guò)integer,小數(shù)部分的位數(shù)不能超過(guò)fraction
@Past必須是 日期 ,且小于當(dāng)前日期
@Future必須是 日期 ,且為將來(lái)的日期
@Size(max,min)字符長(zhǎng)度必須在min到max之間
@Pattern(regex=,flag=)必須符合指定的正則表達(dá)式
@NotEmpty必須不為null且不為空(字符串長(zhǎng)度不為0、集合大小不為0)
@NotBlank必須不為空(不為null、去除首位空格后長(zhǎng)度不為0),不同于@NotEmpty,@NotBlank只應(yīng)用于字符串且在比較時(shí)會(huì)去除字符串的空格
@Email必須為Email,也可以通過(guò)正則表達(dá)式和flag指定自定義的email格式

UserInfo

package com.zero.check.query;
import lombok.Data;
import org.hibernate.validator.constraints.EAN;
import org.springframework.stereotype.Component;
import javax.validation.Valid;
import javax.validation.constraints.*;
import java.util.List;
/**
 * @Description:
 * @author: wei.wang
 * @since: 2019/11/21 15:05
 * @history: 1.2019/11/21 created by wei.wang
 */
@Component
@Data
public class UserInfo {
  @NotBlank(message = "主鍵不能為空")
  @Pattern(regexp = "^[1-9]\\d*$",message = "主鍵范圍不正確")
  private String id;
  @Valid
  @NotEmpty(message = "用戶列表不能為空")
  private List<User> userList;
  @NotNull(message = "權(quán)限不能為空")
  @Min(value = 1, message = "權(quán)限范圍為[1-99]")
  @Max(value = 99, message = "權(quán)限范圍為[1-99]")
  private Long roleId;
}

User

package com.zero.check.query;

import lombok.Data;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
 * @Description:
 * @author: wei.wang
 * @since: 2019/11/21 16:03
 * @history: 1.2019/11/21 created by wei.wang
 */
@Component
@Data
public class User {
  @NotBlank(message = "用戶工號(hào)不能為空")
  private String userId;
  @NotBlank(message = "用戶名稱不能為空")
  private String userName;
  public String getUserId() {
    return userId;
  }
  public void setUserId(String userId) {
    this.userId = userId;
  }
  public String getUserName() {
    return userName;
  }
  public void setUserName(String userName) {
    this.userName = userName;
  }
}

2.Controller層

在需要校驗(yàn)的pojo前邊添加@Validated,在需要校驗(yàn)的pojo后邊添加BindingResult br接收校驗(yàn)出錯(cuò)信息,需要注意的是, BindingResult result一定要跟在 @Validated 注解對(duì)象的后面(必須是實(shí)體類),而且當(dāng)有多個(gè)@Validated注解時(shí),每個(gè)注解對(duì)象后面都需要添加一個(gè) BindingResult,而實(shí)際使用時(shí)由于在WebLogAspect切點(diǎn)讀取了請(qǐng)求數(shù)據(jù),會(huì)導(dǎo)致在Controller層請(qǐng)求參數(shù)中讀不到數(shù)據(jù),這里需要修改其他內(nèi)容,詳見(jiàn)Git

DataCheckController
package com.zero.check.controller;
import com.zero.check.query.User;
import com.zero.check.query.UserInfo;
import com.zero.check.utils.Response;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.*;
import javax.validation.Valid;
/**
 * @Description:
 * @author: wei.wang
 * @since: 2019/11/21 14:57
 * @history: 1.2019/11/21 created by wei.wang
 */
@RestController
@RequestMapping(value = "/check")
public class DataCheckController {
  @PostMapping(value = "/userValidPost")
  public Response queryUserPost(@Valid @RequestBody UserInfo userInfo, BindingResult result) {
    return Response.ok().setData("Hello " + userInfo.getId());
  }
  @GetMapping(value = "/userValidGet")
  public Response queryUserGet(@Valid User user, BindingResult result) {
    return Response.ok().setData("Hello " + user.getUserName());
  }
}

3.AOP

定義切點(diǎn)@Pointcut("execution( com.zero.check.controller.. (..))"),定義后可監(jiān)控com.zero.check.controller包和子包里任意方法的執(zhí)行

如果輸入?yún)?shù)不能通過(guò)校驗(yàn),就直接拋出異常,由于定義了UserInfoHandler攔截器,可以攔截處理校驗(yàn)錯(cuò)誤,這樣就可以省略大量的非空判斷,讓Controller層專注業(yè)務(wù)代碼,并且將日志集中在WebLogAspect中處理,不會(huì)因?yàn)橛涗浫罩緦?dǎo)致要到處改代碼

if (bindingResult.hasErrors()) {
      FieldError error = bindingResult.getFieldError();
      throw new UserInfoException(Response.error(error.getDefaultMessage()).setData(error));
    }

UserInfoHandler
package com.zero.check.handler;

import com.zero.check.exception.UserInfoException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;

/**
 * @Description:
 * @author: wei.wang
 * @since: 2019/11/21 15:04
 * @history: 1.2019/11/21 created by wei.wang
 */
@RestControllerAdvice
public class UserInfoHandler {

  /**
   * 校驗(yàn)錯(cuò)誤攔截處理
   *
   * @param e 錯(cuò)誤信息集合
   * @return 錯(cuò)誤信息
   */
  @ExceptionHandler(UserInfoException.class)
  public Object handle(UserInfoException e) {
    return e.getR();
  }
}

WebLogAspect

package com.zero.check.aspect;

import com.alibaba.fastjson.JSON;
import com.zero.check.exception.UserInfoException;
import com.zero.check.utils.Response;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.validation.FieldError;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
/**
 * @Description:
 * @author: wei.wang
 * @since: 2019/11/21 13:47
 * @history: 1.2019/11/21 created by wei.wang
 */
@Aspect
@Component
public class WebLogAspect {
  private Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
  private final String REQUEST_GET = "GET";
  private final String REQUEST_POST = "POST";
  /**
   * 定義切點(diǎn),切點(diǎn)為com.zero.check.controller包和子包里任意方法的執(zhí)行
   */
  @Pointcut("execution(* com.zero.check.controller..*(..))")
  public void webLog() {
  }
  /**
   * 前置通知,在切點(diǎn)之前執(zhí)行的通知
   *
   * @param joinPoint 切點(diǎn)
   */
  @Before("webLog() &&args(..,bindingResult)")
  public void doBefore(JoinPoint joinPoint, BindingResult bindingResult) {
    if (bindingResult.hasErrors()) {
      FieldError error = bindingResult.getFieldError();
      throw new UserInfoException(Response.error(error.getDefaultMessage()).setData(error));
    }
    //獲取請(qǐng)求參數(shù)
    try {
      String reqBody = this.getReqBody();
      logger.info("REQUEST: " + reqBody);
    } catch (Exception ex) {
      logger.info("get Request Error: " + ex.getMessage());
    }
  }
  /**
   * 后置通知,切點(diǎn)后執(zhí)行
   *
   * @param ret
   */
  @AfterReturning(returning = "ret", pointcut = "webLog()")
  public void doAfterReturning(Object ret) {
    //處理完請(qǐng)求,返回內(nèi)容
    try {
      logger.info("RESPONSE: " + JSON.toJSONString(ret));
    } catch (Exception ex) {
      logger.info("get Response Error: " + ex.getMessage());
    }
  }
  /**
   * 返回調(diào)用參數(shù)
   *
   * @return ReqBody
   */
  private String getReqBody() {
    //從獲取RequestAttributes中獲取HttpServletRequest的信息
    HttpServletRequest request = this.getHttpServletRequest();
    //獲取請(qǐng)求方法GET/POST
    String method = request.getMethod();
    Optional.ofNullable(method).orElse("UNKNOWN");
    if (REQUEST_POST.equals(method)) {
      return this.getPostReqBody(request);
    } else if (REQUEST_GET.equals(method)) {
      return this.getGetReqBody(request);
    }
    return "get Request Parameter Error";
  }
  /**
   * 獲取request
   * Spring對(duì)一些(如RequestContextHolder、TransactionSynchronizationManager、LocaleContextHolder等)中非線程安全狀態(tài)的bean采用ThreadLocal進(jìn)行處理
   * 讓它們也成為線程安全的狀態(tài)
   *
   * @return
   */
  private HttpServletRequest getHttpServletRequest() {
    //獲取RequestAttributes
    RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
    return (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
  }
  /**
   * 獲取GET請(qǐng)求數(shù)據(jù)
   *
   * @param request
   * @return
   */
  private String getGetReqBody(HttpServletRequest request) {
    Enumeration<String> enumeration = request.getParameterNames();
    Map<String, String> parameterMap = new HashMap<>(16);
    while (enumeration.hasMoreElements()) {
      String parameter = enumeration.nextElement();
      parameterMap.put(parameter, request.getParameter(parameter));
    }
    return parameterMap.toString();
  }
  /**
   * 獲取POST請(qǐng)求數(shù)據(jù)
   *
   * @param request
   * @return 返回POST參數(shù)
   */
  private String getPostReqBody(HttpServletRequest request) {
    StringBuilder stringBuilder = new StringBuilder();
    try (InputStream inputStream = request.getInputStream();
       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream))) {
      char[] charBuffer = new char[128];
      int bytesRead = -1;
      while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
        stringBuilder.append(charBuffer, 0, bytesRead);
      }
    } catch (IOException e) {
      logger.info("get Post Request Parameter err : " + e.getMessage());
    }
    return stringBuilder.toString();
  }
}

4.測(cè)試

POST接口

localhost:9004/check/userValidPost

請(qǐng)求參數(shù)

{
  "id":"12",
  "userList": [
    {
      "userId": "Google",
      "userName": "http://www.google.com"
    },
    {
      "userId": "S",
      "userName": "http://www.SoSo.com"
    },
    {
      "userId": "SoSo",
      "userName": "http://www.SoSo.com"
    }
  ],
  "roleId":"11"
}

返回結(jié)果

{
  "code": "ok",
  "data": "Hello 12",
  "requestid": "706cd81db49d4c9795e5457cebb1ba8c"
}

請(qǐng)求參數(shù)

{
  "id":"1A2",
  "userList": [
    {
      "userId": "Google",
      "userName": "http://www.google.com"
    },
    {
      "userId": "S",
      "userName": "http://www.SoSo.com"
    },
    {
      "userId": "SoSo",
      "userName": "http://www.SoSo.com"
    }
  ],
  "roleId":"11"
}

返回結(jié)果

{
  "code": "error",
  "message": "主鍵范圍不正確",
  "data": {
    "codes": [
      "Pattern.userInfo.id",
      "Pattern.id",
      "Pattern.java.lang.String",
      "Pattern"
    ],
    "arguments": [
      {
        "codes": [
          "userInfo.id",
          "id"
        ],
        "arguments": null,
        "defaultMessage": "id",
        "code": "id"
      },
      [],
      {
        "defaultMessage": "^[1-9]\\d*$",
        "arguments": null,
        "codes": [
          "^[1-9]\\d*$"
        ]
      }
    ],
    "defaultMessage": "主鍵范圍不正確",
    "objectName": "userInfo",
    "field": "id",
    "rejectedValue": "1A2",
    "bindingFailure": false,
    "code": "Pattern"
  },
  "requestid": "076c899495b448b59f1b133efd130061"
}

控制臺(tái)輸出

可以看到第一次請(qǐng)求時(shí)WebLogAspect成功打印了請(qǐng)求數(shù)據(jù)和返回結(jié)果,而第二次因?yàn)闆](méi)有通過(guò)校驗(yàn),沒(méi)有進(jìn)入WebLogAspect,所以沒(méi)有打印數(shù)據(jù)

2019-11-21 22:50:43.283 INFO 94432 --- [nio-9004-exec-2] com.zero.check.aspect.WebLogAspect    : REQUEST: {
  "id":"1",
  "userList": [
    {
      "userId": "Google",
      "userName": "http://www.google.com"
    },
    {
      "userId": "S",
      "userName": "http://www.SoSo.com"
    },
    {
      "userId": "SoSo",
      "userName": "http://www.SoSo.com"
    }
  ],
  "roleId":"11"
}
2019-11-21 22:50:43.345 INFO 94432 --- [nio-9004-exec-2] com.zero.check.aspect.WebLogAspect    : RESPONSE: {"code":"ok","data":"Hello 1","requestid":"286174a075c144eeb0de0b8dbd7c1851"}

GET接口

localhost:9004/check/userValidGet?userId=a&userName=zero

返回結(jié)果

{
  "code": "ok",
  "data": "Hello zero",
  "requestid": "9b5ea9bf1db64014b0b4d445d8baf9dc"
}
localhost:9004/check/userValidGet?userId=a&userName=

返回結(jié)果

{
  "code": "error",
  "message": "用戶名稱不能為空",
  "data": {
    "codes": [
      "NotBlank.user.userName",
      "NotBlank.userName",
      "NotBlank.java.lang.String",
      "NotBlank"
    ],
    "arguments": [
      {
        "codes": [
          "user.userName",
          "userName"
        ],
        "arguments": null,
        "defaultMessage": "userName",
        "code": "userName"
      }
    ],
    "defaultMessage": "用戶名稱不能為空",
    "objectName": "user",
    "field": "userName",
    "rejectedValue": "",
    "bindingFailure": false,
    "code": "NotBlank"
  },
  "requestid": "5677d93c084d418e88cf5bb8547c5a2e"
}

控制臺(tái)輸出

可以看到第一次請(qǐng)求時(shí)WebLogAspect成功打印了請(qǐng)求和返回結(jié)果,而第二次因?yàn)闆](méi)有通過(guò)校驗(yàn),沒(méi)有進(jìn)入WebLogAspect,所以沒(méi)有打印數(shù)據(jù)

2019-11-21 23:18:50.755 INFO 94432 --- [nio-9004-exec-9] com.zero.check.aspect.WebLogAspect    : REQUEST: {userName=zero, userId=a}
2019-11-21 23:18:50.756 INFO 94432 --- [nio-9004-exec-9] com.zero.check.aspect.WebLogAspect    : RESPONSE: {"code":"ok","data":"Hello zero","requestid":"422edc9cd59d45bea275e579a67ccd0c"}

關(guān)于“Springboot如何使用@Valid 和AOP做參數(shù)校驗(yàn)及日志輸出問(wèn)題”這篇文章就分享到這里了,希望以上內(nèi)容可以對(duì)大家有一定的幫助,使各位可以學(xué)到更多知識(shí),如果覺(jué)得文章不錯(cuò),請(qǐng)把它分享出去讓更多的人看到。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI