目錄
利用AOP實現參數攔截:
一、??HTTP請求進入Controller?(發送郵件驗證碼)
二、AOP切面觸發
1. 切面攔截(GlobalOperactionAspect.class)
method.getAnnotation()??
?null == interceptor?判斷??
2.參數校驗注解
3. 參數校驗入口??(valiadateParams方法)
三. 對象類型遞歸校驗??(checkObValue方法)[這段代碼沒有調用這個方法,下面有解釋]
四. 基礎類型校驗??(checkValue方法)
五、工具類調用
六、流程圖?
?編輯
七、典型校驗失敗場景示例
六、設計亮點解析
總結流程
疑問:
利用AOP實現參數攔截:
一、??HTTP請求進入Controller?(發送郵件驗證碼)
@RequestMapping("/sendEmailCode")
@GloballInterceptor(checkParams=true) // 觸發AOP攔截
public ResponseVO sendEmailCode(HttpSession session, @VerifyParam(required = true) String email,@VerifyParam(required = true) String checkCode,@VerifyParam(required = true) Integer type){// 實際方法體執行前會先被AOP攔截
}
二、AOP切面觸發
0.首先我們先定義一個切點方法
@Pointcut("@annotation(com.cjl.annotation.GloballInterceptor)")// 表示下面方法為切點方法private void requestIntercector() {System.out.println("請求攔截");}
- 切點(
@Pointcut
)??:定義“在哪里攔截”,不包含邏輯。 - ??增強(
@Before
/@Around
)??:定義“攔截后做什么”,需引用切點。 - ??注解匹配??:通過?
@annotation
?實現聲明式攔截,避免硬編碼。
1. 切面攔截(GlobalOperactionAspect.class)
這是一個全局攔截器
@Before("requestIntercector()") // 聲明在切點方法執行前運行
public void interceptor(JoinPoint point) throws BusinessException {try {// 1. 獲取目標方法元信息Object target = point.getTarget(); // 獲取被代理的目標對象實例Object[] arguments = point.getArgs(); // 獲取方法調用參數值數組String methodName = point.getSignature().getName(); // 獲取目標方法名稱// 2. 通過方法簽名獲取精確的方法定義(考慮方法重載情況)MethodSignature signature = (MethodSignature) point.getSignature();Class<?>[] parameterTypes = signature.getMethod().getParameterTypes();Method method = target.getClass().getMethod(methodName, parameterTypes);// 3. 檢查方法上的攔截器注解GloballInterceptor interceptor = method.getAnnotation(GloballInterceptor.class);if (null == interceptor) {return; // 無攔截注解則直接放行}// 4. 執行參數校驗(根據注解配置決定)if (interceptor.checkParams()) {valiadateParams(method, arguments); // 進入核心校驗流程}} catch (BusinessException e) {// 業務級異常處理(如參數校驗失敗)log.error("全局攔截器攔截到業務異常", e);throw e; // 原樣拋出保持異常鏈} catch (Exception e) {// 系統級異常處理(如反射異常)log.error("全局攔截器發生系統異常", e);throw new BusinessException("系統繁忙,請稍后重試");} catch (Throwable e) {// 兜底處理所有錯誤(包括Error)log.error("全局攔截器捕獲不可預期錯誤", e);throw new BusinessException("系統服務不可用");}
}
method.getAnnotation()
??
- ??作用??:通過反射獲取方法上的?
@GloballInterceptor
?注解實例
?null == interceptor
?判斷??
- ??條件成立??:表示該方法??沒有標注???
@GloballInterceptor
- ??執行邏輯??:直接退出攔截器,??不進行任何校驗??,讓方法正常執行
2.參數校驗注解
@Retention(RetentionPolicy.RUNTIME)// 表示注解在運行時存在
@Target({ElementType.PARAMETER,ElementType.FIELD})// 表示該注解用于方法參數和字段上
public @interface VerifyParam {int min() default -1;int max() default -1;boolean required() default true; // 是否必傳//正則校驗VerifyRegexEnum regex() default VerifyRegexEnum.NO;//默認不校驗
}
3. 參數校驗入口??(valiadateParams方法)
private void valiadateParams(Method method, Object[] arguments) throws BusinessException {// 獲取方法的所有參數定義Parameter[] parameters = method.getParameters();// 遍歷每個參數for (int i = 0; i < parameters.length; i++) {Parameter parameter = parameters[i];Object value = arguments[i];// 獲取參數上的校驗注解VerifyParam verifyParam = parameter.getAnnotation(VerifyParam.class);if (null == verifyParam) {continue; // 無校驗注解則跳過}// 基本數據類型校驗(String/Long/Integer)if (TyPE_STRING.equals(parameter.getType().getName())|| TyPE_LONG.equals(parameter.getType().getName())|| TyPE_INTEGER.equals(parameter.getType().getName())) {checkValue(value, verifyParam);}// 對象類型校驗else {checkObValue(parameter, value);}}}
三. 對象類型遞歸校驗??(checkObValue方法)[這段代碼沒有調用這個方法,下面有解釋]
private void checkObValue(Parameter parameter, Object value) throws BusinessException {try {// 1. 獲取參數的實際類型(支持泛型)String typeName = parameter.getParameterizedType().getTypeName();Class<?> classz = Class.forName(typeName); // 加載類定義// 2. 遍歷對象的所有字段Field[] fields = classz.getDeclaredFields();for (Field field : fields) {// 3. 檢查字段上的校驗注解VerifyParam fieldVerifyParam = field.getAnnotation(VerifyParam.class);if (fieldVerifyParam == null) {continue; // 無注解字段跳過}// 4. 獲取字段值(突破private限制)field.setAccessible(true);Object resultValue = field.get(value);// 5. 遞歸校驗字段值checkValue(resultValue, fieldVerifyParam); // 調用基礎校驗方法}} catch (BusinessException e) {// 透傳業務校驗異常log.error("對象參數校驗業務異常", e);throw e;} catch (Exception e) {// 處理反射等系統異常log.error("對象參數校驗系統異常", e);throw new BusinessException(ResponseCodeEnum.CODE_600);}}
四. 基礎類型校驗??(checkValue方法)
private void checkValue(Object value, VerifyParam verifyParam) throws BusinessException {/*** 空值檢測準備*/Boolean isEmpty = value==null || StringTools.isEmpty(value.toString());Integer length = value==null?null:value.toString().length();/*** 校驗空*/if(isEmpty && verifyParam.required()){throw new BusinessException(ResponseCodeEnum.CODE_600);}/*** 校驗長度*/if(!isEmpty && (verifyParam.max() != -1&& verifyParam.max()< length || verifyParam.min() != -1 && verifyParam.min()>length)){throw new BusinessException(ResponseCodeEnum.CODE_600);}/*** 校驗正則*/if(!isEmpty && !StringTools.isEmpty(verifyParam.regex().getRegex())&& !VerifyUtils.verify(verifyParam.regex(),String.valueOf(value))){throw new BusinessException(ResponseCodeEnum.CODE_600);}}
五、工具類調用
1.空值判斷(StringTools)?
public static boolean verify(VerifyRegexEnum regex, String value) {// 調用重載方法(圖片中定義的EMAIL/PASSWORD規則在此生效)return verify(regex.getRegex(), value); // ← 使用枚舉中的正則表達式
}private static boolean verify(String regex, String value) {Pattern p = Pattern.compile(regex); // ← 編譯正則表達式(如EMAIL的復雜規則)return p.matcher(value).matches(); // ← 執行實際匹配
}
2.正則校驗(VerifyUtils)?
// 圖片中定義的枚舉校驗規則
public enum VerifyRegexEnum {EMAIL("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$", "郵箱格式錯誤"),PASSWORD("^(?=.*\\d)(?=.*[a-zA-Z]).{8,18}$", "需包含字母和數字,8-18位");// 當@VerifyParam(regex=VerifyRegexEnum.EMAIL)時:// 實際使用的正則表達式就是EMAIL枚舉實例中的regex值
}
六、流程圖?
七、典型校驗失敗場景示例
-
??空值校驗失敗??
- 調用:
sendEmailCode(null, "123", 0)
- 拋出:
BusinessException("參數不能為空")
- 調用:
-
??郵箱格式錯誤??
- 調用:
sendEmailCode("invalid#email", "123", 0)
- 匹配:EMAIL正則表達式失敗
- 拋出:
BusinessException("郵箱格式錯誤")
(消息來自枚舉的desc字段)
- 調用:
八、設計亮點解析
-
??雙校驗層設計??
- 前端:基礎非空校驗(快速反饋)
- 后端:AOP+正則深度校驗(安全兜底)
-
??規則集中管理??
// 修改密碼規則只需調整枚舉即可全局生效 PASSWORD("^(?=.*\\d)(?=.*[a-z])(?=.*[A-Z]).{8,20}$", "需包含大小寫字母和數字")
-
??遞歸對象校驗??
支持對嵌套對象的字段級校驗:public void update(@VerifyParam UserDTO user) {// 會自動校驗UserDTO中所有帶@VerifyParam的字段 }
總結流程
🔀 ??參數校驗流程全鏈路總結??:
HTTP請求 → @GloballInterceptor觸發AOP?
→ 解析@VerifyParam注解?
→ 分發校驗(基本類型→checkValue() / 對象類型→遞歸checkObValue())?
→ 調用StringTools/VerifyRegexEnum工具
→ 校驗失敗拋BusinessException?
→ 校驗通過執行業務邏
??核心箭頭圖??:
Controller
→ 🌐 AOP切面
→ 🔍 參數掃描
→ ?? 規則匹配
→ ?/? 校驗結果
→ ? 業務邏輯/異常返回
疑問:
1.為什么遍歷對象所有帶@VerifyParam注解的字段進行校驗為什么是遞歸?
答:遍歷對象字段校驗”本身不是遞歸,但當字段本身又是對象時,需要再次觸發相同的校驗流程??,這才是遞歸的本質
如果變量是普通字段(checkvalue(user對象)),會采取普通字段校驗,比如:
// 簡單User對象(無嵌套)
public class User {@VerifyParam String name; // 基本類型字段@VerifyParam Integer age; // 基本類型字段
}
如果變量是??嵌套對象字段->觸發遞歸,比如:
// 嵌套的Order對象,里面有User
public class Order {@VerifyParam User user; // 對象類型字段!
}
校驗流程??:
checkObValue(Order對象)
?→ 發現user
字段是對象類型- ??遞歸調用???
checkObValue(user)
?→ 進入User類的字段校驗 - 如果
User
類中還有對象字段,繼續向下遞歸
🔁 ??這才是真正的遞歸調用鏈!
2.@Pointcut切點和@GloballInterceptor是什么關系?
🔍 ??準確關系說明??:
@Pointcut
?和?@GloballInterceptor
?是??觀察者與被觀察者??的關系,具體流程如下:
-
??你在方法上標注?
@GloballInterceptor
?→ 聲明"這個方法需要被攔截 -
@Pointcut
?像掃描儀一樣持續監控所有方法 -
通過表達式?
@annotation(com.cjl.annotation.GloballInterceptor)
?專門尋找帶該注解的方法 -
// 切面內部工作原理
if (當前方法有@GloballInterceptor注解) {
? ? 執行@Before/...增強邏輯(interceptor方法);
}
3.這個JoinPoint point參數傳進來的是什么?
@Before("requestIntercector()")?public void interceptorDO(JoinPoint point) throws BusinessException {...
}
解析一下:
@Before("requestIntercector()")
public void interceptorDO(JoinPoint point) {// 1. 獲取目標方法實例Method method = ((MethodSignature) point.getSignature()).getMethod();// 輸出:public com.cjl.vo.ResponseVO com.cjl.controller.SessionController.sendEmailCode(...)// 2. 獲取方法參數值數組(按聲明順序)Object[] args = point.getArgs(); // 內容:[HttpSession實例, "user@example.com", "A7b9", 1]// 3. 獲取具體參數HttpSession session = (HttpSession) args[0]; // 第一個參數String email = (String) args[1]; // 第二個參數String checkCode = (String) args[2]; // 第三個參數Integer type = (Integer) args[3]; // 第四個參數// 4. 獲取注解配置VerifyParam emailVerify = method.getParameters()[1].getAnnotation(VerifyParam.class);// 獲取email參數的@VerifyParam(required=true)注解
}
4.它怎么知道我要不要攔截檢驗