前言
對于初學者們,對于接口的參數判空多多少少都是有些膈應,因為每次寫個接口,拿那幾個參,就得if else。
那麼該篇教學,就可以一定程度解決這個問題。
正文
該篇文章涉及到的:
1.自定義注解 ,用于标注需要進行校驗的參數
2.AOP配合自定義注解使用
3.實作公共的傳回參
4.實作全局異常捕獲
先看整體我們需要做的東西有什麼:
隻要完成這兩個檔案夾裡面的,那麼到了新的項目,你隻需要把這兩個檔案夾的東西直接丢進去即可。
接下來開始敲代碼吧,
首先是pom.xml,一些相關的jar:
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.9</version>
</dependency>
<!--fastjson-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.58</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
建立自定義注解,ParamCheck.java :
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Author : JCccc
* @CreateTime : 2020/5/14
* @Description :
**/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamCheck {
/**
* 是否非空,預設不能為空
*/
boolean notNull() default true;
/**
* 預設值
* @return
*/
String defaultValue() default "";
}
簡單描述:
ElementType.PARAMETER 使用于參數
boolean notNull() default true; 要求參數不為空,預設開啟,可以自己傳
String defaultValue() default ""; 預設值,預設設定 "",可以自己傳
接下來建立 參數校驗的AOP實作類,ParamValidAop.java:
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* @Author : JCccc
* @CreateTime : 2020/5/14
* @Description :
**/
@Component
@Aspect
public class ParamValidAop {
/**
* 定義有一個切入點,範圍為web包下的類
*/
@Pointcut("execution(public * com.bsapple.vshop.controller..*.*(..))")
public void checkParam() {
}
@Before("checkParam()")
public void doBefore(JoinPoint joinPoint) {
}
/**
* 檢查參數是否為空
*/
@Around("checkParam()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
MethodSignature signature = ((MethodSignature) pjp.getSignature());
//得到攔截的方法
Method method = signature.getMethod();
//擷取方法參數注解,傳回二維數組是因為某些參數可能存在多個注解
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
if (parameterAnnotations == null || parameterAnnotations.length == 0) {
return pjp.proceed();
}
//擷取方法參數名
String[] paramNames = signature.getParameterNames();
//擷取參數值
Object[] paranValues = pjp.getArgs();
//擷取方法參數類型
Class<?>[] parameterTypes = method.getParameterTypes();
for (int i = 0; i < parameterAnnotations.length; i++) {
for (int j = 0; j < parameterAnnotations[i].length; j++) {
//如果該參數前面的注解不為空并且是ParamCheck的執行個體,并且notNull()=true,并且預設值為空,則進行非空校驗
if (parameterAnnotations[i][j] != null && parameterAnnotations[i][j] instanceof ParamCheck && ((ParamCheck) parameterAnnotations[i][j]).notNull() && StringUtils.isEmpty(((ParamCheck)parameterAnnotations[i][j]).defaultValue())) {
paramIsNull(paramNames[i], paranValues[i], parameterTypes[i] == null ? null : parameterTypes[i].getName());
break;
}
//如果該參數前面的注解不為空并且是ParamCheck的執行個體,并且預設值不為空,并且參數值為空,則進行賦預設值
if(parameterAnnotations[i][j] != null && parameterAnnotations[i][j] instanceof ParamCheck && !StringUtils.isEmpty(((ParamCheck)parameterAnnotations[i][j]).defaultValue()) && (paranValues[i] == null || StringUtils.isEmpty(paranValues[i].toString()))){
paranValues[i] = putParam(((ParamCheck)parameterAnnotations[i][j]).defaultValue(), parameterTypes[i]);
}
}
}
return pjp.proceed(paranValues);
}
/**
* 在切入點return内容之後切入内容(可以用來對處理傳回值做一些加工處理)
*
* @param joinPoint
*/
@AfterReturning("checkParam()")
public void doAfterReturning(JoinPoint joinPoint) {
}
/**
* 參數非空校驗,如果參數為空,則抛出ParamIsNullException異常
* @param paramName
* @param value
* @param parameterType
*/
private void paramIsNull(String paramName, Object value, String parameterType) {
if (value == null || "".equals(value.toString().trim())) {
throw new ParamIsNullException(paramName, parameterType,"參數為空");
}
}
private Object putParam(Object value, Class<?> parameterType) {
return CastValueTypeUtil.parseValue(parameterType, value.toString());
}
}
需要注意,這個路徑是你準備添加校驗的controller的路徑,改成你自己的:
然後是校驗參數裡面使用到的參數轉換工具類,CastValueTypeUtil.java:
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
*轉換object類型
**/
public class CastValueTypeUtil {
public static Object parseValue(Class<?> parameterTypes, String value) {
if(value==null || value.trim().length()==0){
return null;
}
value = value.trim();
if (Byte.class.equals(parameterTypes) || Byte.TYPE.equals(parameterTypes)) {
return parseByte(value);
} else if (Boolean.class.equals(parameterTypes) || Boolean.TYPE.equals(parameterTypes)) {
return parseBoolean(value);
}/* else if (Character.class.equals(fieldType) || Character.TYPE.equals(fieldType)) {
return value.toCharArray()[0];
}*/ else if (String.class.equals(parameterTypes)) {
return value;
} else if (Short.class.equals(parameterTypes) || Short.TYPE.equals(parameterTypes)) {
return parseShort(value);
} else if (Integer.class.equals(parameterTypes) || Integer.TYPE.equals(parameterTypes)) {
return parseInt(value);
} else if (Long.class.equals(parameterTypes) || Long.TYPE.equals(parameterTypes)) {
return parseLong(value);
} else if (Float.class.equals(parameterTypes) || Float.TYPE.equals(parameterTypes)) {
return parseFloat(value);
} else if (Double.class.equals(parameterTypes) || Double.TYPE.equals(parameterTypes)) {
return parseDouble(value);
} else if (Date.class.equals(parameterTypes)) {
return parseDate(value);
} else {
throw new RuntimeException("request illeagal type, type must be Integer not int Long not long etc, type=" + parameterTypes);
}
}
public static Byte parseByte(String value) {
try {
value = value.replaceAll(" ", "");
return Byte.valueOf(value);
} catch(NumberFormatException e) {
throw new RuntimeException("parseByte but input illegal input=" + value, e);
}
}
public static Boolean parseBoolean(String value) {
value = value.replaceAll(" ", "");
if (Boolean.TRUE.toString().equalsIgnoreCase(value)) {
return Boolean.TRUE;
} else if (Boolean.FALSE.toString().equalsIgnoreCase(value)) {
return Boolean.FALSE;
} else {
throw new RuntimeException("parseBoolean but input illegal input=" + value);
}
}
public static Integer parseInt(String value) {
try {
value = value.replaceAll(" ", "");
return Integer.valueOf(value);
} catch(NumberFormatException e) {
throw new RuntimeException("parseInt but input illegal input=" + value, e);
}
}
public static Short parseShort(String value) {
try {
value = value.replaceAll(" ", "");
return Short.valueOf(value);
} catch(NumberFormatException e) {
throw new RuntimeException("parseShort but input illegal input=" + value, e);
}
}
public static Long parseLong(String value) {
try {
value = value.replaceAll(" ", "");
return Long.valueOf(value);
} catch(NumberFormatException e) {
throw new RuntimeException("parseLong but input illegal input=" + value, e);
}
}
public static Float parseFloat(String value) {
try {
value = value.replaceAll(" ", "");
return Float.valueOf(value);
} catch(NumberFormatException e) {
throw new RuntimeException("parseFloat but input illegal input=" + value, e);
}
}
public static Double parseDouble(String value) {
try {
value = value.replaceAll(" ", "");
return Double.valueOf(value);
} catch(NumberFormatException e) {
throw new RuntimeException("parseDouble but input illegal input=" + value, e);
}
}
public static Date parseDate(String value) {
try {
String datePattern = "yyyy-MM-dd HH:mm:ss";
SimpleDateFormat dateFormat = new SimpleDateFormat(datePattern);
return dateFormat.parse(value);
} catch(ParseException e) {
throw new RuntimeException("parseDate but input illegal input=" + value, e);
}
}
}
然後是建立一個自定義異常,專門用于校驗參數為空的時候抛出,ParamIsNullException.java:
/**
* @Author : JCccc
* @CreateTime : 2020/5/14
* @Description :
**/
public class ParamIsNullException extends RuntimeException {
private final String parameterName;
private final String parameterType;
private final String message;
public ParamIsNullException(String parameterName, String parameterType, String message) {
super();
this.parameterName = parameterName;
this.parameterType = parameterType;
this.message = message;
}
@Override
public String getMessage() {
return "請求參數類型:" + this.parameterType + ",參數名: \'" + this.parameterName + message;
}
public final String getParameterName() {
return this.parameterName;
}
public final String getParameterType() {
return this.parameterType;
}
}
到這裡,其實可以看到自定義注解以及AOP已經實作完畢。
接下來是做一下,統一的傳回參以及全局異常捕獲,
建立接口BaseErrorInfoInterface.java:
/**
* @Author:JCccc
* @Description:此接口用于傳回碼枚舉使用
* @Date: created in 15:11 2019/5/3
*/
public interface BaseErrorInfoInterface {
/** 錯誤碼*/
String getResultCode();
/** 錯誤描述*/
String getResultMsg();
}
傳回碼的枚舉類,CommonEnum.java:
/**
* @Author:JCccc
* @Description:
* @Date: created in 15:13 2019/5/3
*/
public enum CommonEnum implements BaseErrorInfoInterface {
// 資料操作錯誤定義
SUCCESS("200", "成功!"),
BODY_NOT_MATCH("400", "請求的資料格式不符!"),
SIGNATURE_NOT_MATCH("401", "請求的數字簽名不比對!"),
NOT_FOUND("404", "未找到該資源!"),
INTERNAL_SERVER_ERROR("500", "伺服器内部錯誤!"),
SERVER_BUSY("503", "伺服器正忙,請稍後再試!"),
REQUEST_METHOD_SUPPORT_ERROR("40001","目前請求方法不支援");
/**
* 錯誤碼
*/
private String resultCode;
/**
* 錯誤描述
*/
private String resultMsg;
CommonEnum(String resultCode, String resultMsg) {
this.resultCode = resultCode;
this.resultMsg = resultMsg;
}
@Override
public String getResultCode() {
return resultCode;
}
@Override
public String getResultMsg() {
return resultMsg;
}
}
然後是一個簡單的傳回體專用類,ResultBody.java:
import com.alibaba.fastjson.JSONObject;
/**
* @Author:JCccc
* @Description:
* @Date: created in 15:19 2019/5/3
*/
public class ResultBody {
/**
* 響應代碼
*/
private String code;
/**
* 響應消息
*/
private String message;
/**
* 響應結果
*/
private Object result;
public ResultBody() {
}
public ResultBody(BaseErrorInfoInterface errorInfo) {
this.code = errorInfo.getResultCode();
this.message = errorInfo.getResultMsg();
}
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public Object getResult() {
return result;
}
public void setResult(Object result) {
this.result = result;
}
/**
* 成功
*
* @return
*/
public static ResultBody success() {
return success(null);
}
/**
* 成功
* @param data
* @return
*/
public static ResultBody success(Object data) {
ResultBody rb = new ResultBody();
rb.setCode(CommonEnum.SUCCESS.getResultCode());
rb.setMessage(CommonEnum.SUCCESS.getResultMsg());
rb.setResult(data);
return rb;
}
/**
* 失敗
*/
public static ResultBody error(BaseErrorInfoInterface errorInfo) {
ResultBody rb = new ResultBody();
rb.setCode(errorInfo.getResultCode());
rb.setMessage(errorInfo.getResultMsg());
rb.setResult(null);
return rb;
}
/**
* 失敗
*/
public static ResultBody error(String code, String message) {
ResultBody rb = new ResultBody();
rb.setCode(code);
rb.setMessage(message);
rb.setResult(null);
return rb;
}
/**
* 失敗
*/
public static ResultBody error( String message) {
ResultBody rb = new ResultBody();
rb.setCode("-1");
rb.setMessage(message);
rb.setResult(null);
return rb;
}
@Override
public String toString() {
return JSONObject.toJSONString(this);
}
}
最後是自定義的業務異常類,和全局異常捕獲類,
BizException.java
/**
* @Author:JCccc
* @Description:
* @Date: created in 15:18 2019/5/3
*/
public class BizException extends RuntimeException {
private static final long serialVersionUID = 1L;
/**
* 錯誤碼
*/
protected String errorCode;
/**
* 錯誤資訊
*/
protected String errorMsg;
public BizException() {
super();
}
public BizException(BaseErrorInfoInterface errorInfoInterface) {
super(errorInfoInterface.getResultCode());
this.errorCode = errorInfoInterface.getResultCode();
this.errorMsg = errorInfoInterface.getResultMsg();
}
public BizException(BaseErrorInfoInterface errorInfoInterface, Throwable cause) {
super(errorInfoInterface.getResultCode(), cause);
this.errorCode = errorInfoInterface.getResultCode();
this.errorMsg = errorInfoInterface.getResultMsg();
}
public BizException(String errorMsg) {
super(errorMsg);
this.errorMsg = errorMsg;
}
public BizException(String errorCode, String errorMsg) {
super(errorCode);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public BizException(String errorCode, String errorMsg, Throwable cause) {
super(errorCode, cause);
this.errorCode = errorCode;
this.errorMsg = errorMsg;
}
public String getErrorCode() {
return errorCode;
}
public void setErrorCode(String errorCode) {
this.errorCode = errorCode;
}
public String getErrorMsg() {
return errorMsg;
}
public void setErrorMsg(String errorMsg) {
this.errorMsg = errorMsg;
}
public String getMessage() {
return errorMsg;
}
@Override
public Throwable fillInStackTrace() {
return this;
}
}
GlobalExceptionHandler.java:
import com.bsapple.vshop.wholeConfig.paramCheck.ParamIsNullException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MissingServletRequestParameterException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import javax.servlet.http.HttpServletRequest;
/**
* @Author:JCccc
* @Description:
* @Date: created in 15:29 2019/5/3
*/
@RestControllerAdvice
public class GlobalExceptionHandler {
private static final Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* 處理自定義的業務異常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = BizException.class)
@ResponseBody
public ResultBody bizExceptionHandler(HttpServletRequest req, BizException e){
logger.error("發生業務異常!原因是:{}",e.getErrorMsg());
return ResultBody.error(e.getErrorCode(),e.getErrorMsg());
}
/**
* 處理空指針的異常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value =NullPointerException.class)
@ResponseBody
public ResultBody exceptionHandler(HttpServletRequest req, NullPointerException e){
logger.error("發生空指針異常!原因是:",e);
return ResultBody.error(CommonEnum.BODY_NOT_MATCH);
}
/**
* 處理請求方法不支援的異常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = HttpRequestMethodNotSupportedException.class)
@ResponseBody
public ResultBody exceptionHandler2(HttpServletRequest req, HttpRequestMethodNotSupportedException e){
logger.error("發生請求方法不支援異常!原因是:",e);
return ResultBody.error(CommonEnum.REQUEST_METHOD_SUPPORT_ERROR);
}
/**
* 處理請求方法不支援的異常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value = {ParamIsNullException.class,MissingServletRequestParameterException.class})
@ResponseBody
public ResultBody exceptionHandler3(HttpServletRequest req, Exception e){
logger.error("參數為空!原因是:",e);
return ResultBody.error(CommonEnum.SIGNATURE_NOT_MATCH.getResultCode(),e.getMessage());
}
/**
* 處理其他異常
* @param req
* @param e
* @return
*/
@ExceptionHandler(value =Exception.class)
@ResponseBody
public ResultBody exceptionHandler(HttpServletRequest req, Exception e){
logger.error("未知異常!原因是:",e);
return ResultBody.error(CommonEnum.INTERNAL_SERVER_ERROR);
}
}
好了,接下來看看這套實作的東西,效果如何:
給需要的校驗的參數加上我們的自定義注解即可,如:
@GetMapping("/hello1")
public ResultBody hello1(@ParamCheck String name) {
return ResultBody.success();
}
正常通路該接口,
那麼我們試試不傳參數name:
那麼我再試試,自定義注解裡面預設值的使用:
然後再調用接口,不傳name,可以看到預設值賦予成功:
最後再試試,同時用上注解 @RequestParam("userName") 給參數name起了個别名,然後也用上我們的自定義參數校驗注解,如:
調用接口,正常通路:
可以回顧看下我們AOP實作類裡面的代碼,沒錯,就是二維數組可以接受這個參數的多個注解:
簡單的測試效果就到此吧,自己打個debug斷點看一看就ok,該篇就到此吧。