一、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); // 抛出业务异常}}
}
总结
去浏览器直接调用这个路径,没有传参数的话,报错