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