SpringBoot 自定義注意 + AOP實現參數效驗,默認值賦值,和@RequestParam相同功能
寫自定義註解的意義在於,@RequestParam
沒有對參數值進行效驗,如空值;
經過測試發現,@RequestParam
只對本次請求中帶不帶參數名進行了效驗,如參數是?userName=
,@RequestParam
則會放行,只有當userName不存在參數列表中是,纔會提示報錯,這就會導致很多問題,需要後臺每次對數據都進行空值判斷;
又或者前端人員參數爲定義,傳入了一個undefined
,那造成的麻煩更多,所以自定義註解 + AOP來解決這個問題。
開發的這個自定義註解,能實現和@RequestParam
一樣的功能,但是進行了加強,對參數空值或者undefined進行效驗,還可以實現默認值賦值
自定義註解
自定義註解相比怎麼寫應該都知道,其實就是一個接口,在接口上添加一個其他註解,實現了自己的註解
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @Retention 作用:標識此註解的生命週期,有三種可選值
* 1.RetentionPolicy.SOURCE:在源文件中有效(即源文件保留)
* 2.RetentionPolicy.CLASS:在class文件中有效(即class保留)
* 3.RetentionPolicy.RUNTIME:在運行時有效(即運行時保留)
*
* @Target 作用:標識此註解能用在什麼地方
* 1.ElementType.CONSTRUCTOR:用於構造器
* 2.ElementType.FIELD:用於屬性
* 3.ElementType.LOCAL_VARIABLE:用於局部變量
* 4.ElementType.METHOD:用於方法
* 5.ElementType.PACKAGE:用於包
* 6.ElementType.PARAMETER:用於參數
* 7.ElementType.TYPE:用於類、接口(包括註解類型) 或enum聲明
*/
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamCheck {
/**
* 是否非空,默認不能爲空
*/
boolean notNull() default true;
/**
* 默認值
* @return
*/
String defaultValue() default "";
}
自定義註解是寫好了,Spring Boot項目中帶有了AOP的jar包,可以不用引入,接下來寫實現邏輯
用AOP實現註解功能
import com.hm.common.util.cast.CastValueTypeUtil;
import com.hm.common.util.exception.ParamIsNullException;
import com.hm.spss.annotation.ParamCheck;
import org.apache.commons.lang3.StringUtils;
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 java.lang.annotation.Annotation;
import java.lang.reflect.Method;
/**
* 參數效驗AOP
*/
@Component
@Aspect
public class ParamCheckAOP {
/**
* 定義有一個切入點,範圍爲web包下的類
*/
@Pointcut("execution(public * com.hm.spss.controller..*.*(..))")
public void checkParam() {
}
/**
* 方法執行前執行
* @param joinPoint
*/
@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內容之後切入內容(可以用來對處理返回值做一些加工處理)
* TODO 留着做以後處理返回值用
* @param joinPoint
*/
@AfterReturning(value = "checkParam()", returning = "result")
public void doAfterReturning(JoinPoint joinPoint, Object result) {
}
/**
* 參數非空校驗,如果參數爲空,則拋出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, ",不能爲空!");
}
if ("undefined".equals(value.toString().trim())){
throw new ParamIsNullException(paramName, parameterType, ",不能爲undefined!");
}
}
private Object putParam(Object value, Class<?> parameterType){
return CastValueTypeUtil.parseValue(parameterType, value.toString());
}
}
其中有兩個Java類,一個是Value轉換,一個是自定義異常,轉換類的意義在於,默認值在賦予的時候是String字符串,但是實際賦值的時候,給參數賦值,是不確定元素,可以確實的是,這個參數肯定是八大類型中的其中一個
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);
}
}
}
自定義異常類
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;
}
}
以上就是完成了,非常的簡單,接下來看如何使用
@GetMapping("/getRisk")
public Result getRisk(@ParamCheck(defaultValue = "1") Integer page,
@ParamCheck(defaultValue = "10") Integer size,
@ParamCheck(notNull = false) String worksName) {
return ResultUtil.success();
}
結語
當參數名傳了,參數值沒傳時,會拋出異常返回,參數名沒傳也一樣的效果,並且也可以賦予默認值,具體看代碼邏輯實現,也可以自行修改,只描述個大概。