一、AOP是什么?
目的:分離橫切關注點(如日志記錄、事務管理)與核心業務邏輯。
優勢:提高代碼的可讀性和可維護性。
關鍵概念
- 切面(Aspect):包含橫切關注點代碼的模塊。
- 通知(Advice):切面中的具體動作,比如方法調用之前或之后執行的代碼。
- 連接點(Join Point):程序執行的某個具體點,比如方法調用。
- 切入點(Pointcut):定義在哪些連接點應用通知。
二、使用步驟
1.引入庫
代碼如下(示例):
<dependencies><!-- 引入SpringBoot Aop依賴 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- 引入Aspectj依賴 --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.7</version></dependency>
</dependencies>
2.定義注解
定義注解GlobalInterceptor
代碼示例:如下
@Target({ElementType.METHOD})//注解的目標類型是方法
@Retention(RetentionPolicy.RUNTIME)//注解在運行的時候生效
@Documented
@Mapping
public @interface GlobalInterceptor {/*** 校驗參數* @return*/boolean checkParams() default false;
}
?定義注解用來校驗具體參數
@Retention(RetentionPolicy.RUNTIME)//運行時校驗
@Target({ElementType.PARAMETER,ElementType.FIELD})// 指定該注解可以應用的目標類型為參數和字段
public @interface VerifyParam {int min() default -1;//校驗最小長度int max() default -1;//檢驗最大長度boolean required() default false; //校驗是否必傳VerifyRegexEnum regex() default VerifyRegexEnum.NO;//校驗正則,默認狀態是不校驗的}
?可以看到上方的VerifyRegexEnum,這里是一個枚舉,主要是來校驗參數的,那么枚舉代碼示例如下:
public enum VerifyRegexEnum {NO("","不校驗"),EMAII("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$","郵箱"),PASSWORD("^(?=.*\\d)(?=.*[a-zA-Z])[\\da-zA-Z~!@#$號^&* ]{8,}$","只能是數字,字母,特殊字符 8-18位");private String regex;private String desc;VerifyRegexEnum(String regex, String desc) {this.regex = regex;this.desc = desc;}public String getRegex() {return regex;}public String getDesc() {return desc;}
}
?由于這里我的項目中只是簡單的校驗了一下郵箱和密碼,需要的話,大家可以自行加入校驗方式
?3.定義切面類
@Aspect//表明這是一個切面類
@Component("globalOperatcionAspect")// 交給Spring管理
public class GlobalOperatcionAspect {private static final Logger logger = LoggerFactory.getLogger(GlobalOperatcionAspect.class);private static final String[] TYPE_BASE = {"java.lang.String","java.lang.Integer","java.lang.Long"};//@Pointcut 定義切入點表達式,用于匹配目標方法,此處匹配帶有@GlobalInterceptor注解的方法@Pointcut("@annotation(com.easypan.annotation.GlobalInterceptor)")private void requestInterceptor(){// 方法體為空,只是作為一個切入點標識}//@Before 在目標方法執行前執行@Before("requestInterceptor()")public void interceptorDo(JoinPoint point) throws BusinessException {try {Object target = point.getTarget();// 獲取目標對象Object[] arguments = point.getArgs(); // 獲取方法參數String methodName = point.getSignature().getName(); // 獲取方法名Class<?>[] parameterTypes = ((MethodSignature) point.getSignature()).getMethod().getParameterTypes(); // 獲取方法參數類型Method method = target.getClass().getMethod(methodName, parameterTypes); // 獲取目標方法GlobalInterceptor interceptor = method.getAnnotation(GlobalInterceptor.class); // 獲取方法上的全局攔截器注解if (null == interceptor) { // 如果注解為空則不執行攔截器邏輯return;}/*** 檢驗參數*/if (interceptor.checkParams()) { // 如果需要檢驗參數validateParams(method, arguments); // 執行參數校驗}} catch (BusinessException e) {logger.error("全局攔截器異常", e); // 記錄異常日志throw e; // 拋出業務異常} catch (Exception e) {logger.error("全局攔截器異常", e); // 記錄異常日志throw new BusinessException(ResponseCodeEnum.CODE_500); // 拋出業務異常} catch (Throwable e) {logger.error("全局攔截器異常", e); // 記錄異常日志throw new BusinessException(ResponseCodeEnum.CODE_500); // 拋出業務異常}}/*** 檢驗規則* @param method 方法* @param arguments 參數列表*/private void validateParams(Method method, Object[] arguments) {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 (verifyParam == null) { // 如果注解為空則跳過continue;}if (ArrayUtils.contains(TYPE_BASE, parameter.getParameterizedType().getTypeName())) { // 如果是基本類型checkValue(value, verifyParam); // 執行值校驗} else {checkBObjValue(parameter, value); // 執行對象值校驗}}}/*** 對象值校驗* @param parameter 參數* @param value 參數值*/private void checkBObjValue(Parameter parameter, Object value) {try {String typeName = parameter.getParameterizedType().getTypeName(); // 獲取參數類型名Class classz = Class.forName(typeName); // 獲取類對象Field[] fields = classz.getDeclaredFields(); // 獲取類的所有字段for (Field field : fields) { // 遍歷字段VerifyParam fieldVerifyParam = field.getAnnotation(VerifyParam.class); // 獲取字段上的校驗注解if (fieldVerifyParam == null) { // 如果注解為空則跳過continue;}field.setAccessible(true); // 設置字段可訪問Object resultValue = field.get(value); // 獲取字段值checkValue(resultValue, fieldVerifyParam); // 執行值校驗}} catch (BusinessException e) {logger.error("校驗參數失敗", e); // 記錄異常日志throw e; // 拋出業務異常} catch (Exception e) {logger.error("校驗參數失敗", e); // 記錄異常日志throw new BusinessException(ResponseCodeEnum.CODE_600); // 拋出業務異常}}/*** 值校驗* @param value 值* @param verifyParam 校驗參數*/private void checkValue(Object value, VerifyParam verifyParam) {Boolean isEmpty = value == null || StringTools.isEmpty(value.toString()); // 判斷值是否為空Integer length = value == null ? 0 : 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); // 拋出業務異常}}
}
總結
?去瀏覽器直接調用這個路徑,沒有傳參數的話,報錯