一,什么是AOP
AOP(Aspect Orient Programming)是spring框架中的一个重要部分。直译过来就是面向切面编程,这是一种编程思想,作为面向对象的一种补充。其本质就是在不改变源代码的情况下给程序或一组程序动态统一的添加额外功能。
二,AOP的运用场景
-
请求参数验证:在Web应用程序中,AOP参数校验可以用于验证用户提交的请求参数的合法性。例如,您可以使用AOP来验证表单提交的参数是否符合预期的格式、范围或约束条件。
-
接口参数验证:在服务端应用程序中,AOP参数校验可以用于验证接口方法的输入参数的有效性。例如,您可以使用AOP来验证传递给接口方法的参数是否满足预期的要求,例如非空、长度限制、类型匹配等。
-
数据库操作参数验证:在进行数据库操作时,AOP参数校验可以用于验证传递给数据库操作的参数的有效性。例如,您可以使用AOP来验证传递给数据库查询、更新或插入操作的参数是否符合数据库定义的约束条件。
-
身份验证和授权:AOP参数校验还可以用于验证用户身份和权限。例如,您可以使用AOP来验证用户的访问权限,确保只有具有特定权限的用户能够访问某些敏感操作或资源。
-
日志记录和异常处理:AOP参数校验还可以用于记录日志和处理异常。例如,您可以使用AOP来记录参数校验失败的日志,或者在参数校验失败时抛出自定义的异常,以便进行统一的异常处理。
三,举个栗子
1.maven引入依赖
<!--spring context依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--spring aop依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.0.0-M2</version>
</dependency>
<!--spring aspects依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.0-M2</version>
</dependency>
2.定义目标类和目标方法
package com.windking.service;
// 目标类
@Service
public class OrderService {
// 目标方法
public void business(){
System.out.println("订单已生成");
}
}
3.定义切面类
package com.windking.service;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
// 切面类
@Component
@Aspect
public class MyAspect {
@Around("execution(* com.windking.service.OrderService.*(..))")
public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
System.out.println("环绕通知开始");
// 执行目标方法。
proceedingJoinPoint.proceed();
System.out.println("环绕通知结束");
}
@Before("execution(* com.windking.service.OrderService.*(..))")
public void beforeAdvice(){
System.out.println("前置通知");
}
@AfterReturning("execution(* com.windking.service.OrderService.*(..))")
public void afterReturningAdvice(){
System.out.println("后置通知");
}
@AfterThrowing("execution(* com.windking.service.OrderService.*(..))")
public void afterThrowingAdvice(){
System.out.println("异常通知");
}
@After("execution(* com.windking.service.OrderService.*(..))")
public void afterAdvice(){
System.out.println("最终通知");
}
}
4.在spring配置文件spring-aspectj-aop-annotation.xml中启动自动代理
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--开启组件扫描-->
<context:component-scan base-package="com.windking.service"/>
<!--开启自动代理-->
<aop:aspectj-autoproxy proxy-target-class="true"/>
</beans>
5. 测试程序
package com.windking.test;
import com.windking.service.OrderService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class AOPTest {
@Test
public void testAOP(){
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring-aspectj-aop-annotation.xml");
OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
orderService.business();
}
}
6.输出结果:
四,自定义注解实现参数校验(重点内容)
1.我们假定有个用户DTO类
@Data
public class SessionWebUserDto {
private String nickName;
private String userId;
private Boolean isAdmin;
private String avatar;
}
2.定义总拦截器注解
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Mapping
public @interface GlobalInterceptor {
//校验登入
boolean checkLogin() default true;
//校验参数
boolean checkParams() default false;
//校验管理员
boolean checkAdmin() default false;
}
3.定义校验规则注解
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER, ElementType.FIELD})
public @interface VerifyParam {
//校验正则
VerifyRegexEnum regex() default VerifyRegexEnum.NO;
//最小长度
int min() default -1;
//最大长度
int max() default -1;
//是否必填
boolean required() default false;
}
4.定义校验规则枚举类
@Getter
public enum VerifyRegexEnum {
EMAIL("^[\\w-]+(\\.[\\w-]+)*@[\\w-]+(\\.[\\w-]+)+$", "邮箱"),
PASSWORD("^(?=.*\\d)(?=.*[a-zA-Z])[\\da-zA-Z~!@#$%^&*_]{8,}$", "只能是数字,字母,特殊字符 8-18位"),
private String regex;
private String desc;
}
5.切面类实现
public class GlobalOperationAspect {
@PointCut("@annotation(com.windking.annotation.GlobalInterceptor)") // 注解的全类名
private void requestInterceptor() {}
@Before("requestInterceptor()")
public void interceptorDo(JoinPoint point) throws Exception {
// TODO
}
}
6.利用 point
反射获取控制器对象的实例类,被拦截方法的参数值,方法名等。之后再考虑对被拦截的方法的参数依次校验(//TODO的内容)
@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.checkLogin() || interceptor.checkAdmin()) {
checkLogin(interceptor.checkAdmin());
}
//校验参数
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);
}
}
7.接下来讲讲 validateParam
方法。这个方法是实现被拦截方法参数校验的,自然而然我们需要得到参数值以及更为重要的参数注解 。依旧是通过反射来实现。(checkLogin方法省略,原理为检查用户的登录状态和权限。如果用户未登录,将抛出异常;如果用户权限不足,将抛出异常。)
private void validateParams(Method m, Object[] arguments) throws BusinessException {
Parameter[] parameters = m.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 (TYPE_STRING.equals(parameter.getParameterizedType().getTypeName()) || TYPE_LONG.equals(parameter.getParameterizedType().getTypeName()) || TYPE_INTEGER.equals(parameter.getParameterizedType().getTypeName())) {
checkValue(value, verifyParam);
//如果传递的是对象
} else {
checkObjValue(parameter, value);
}
}
}
8.效果
@RequestMapping("/login")
@GlobalInterceptor(checkLogin = false, checkParams = true)
public ResponseVO login(HttpSession session, HttpServletRequest request,
@VerifyParam(required = true, regex = VerifyRegexEnum.EMAIL, max = 150) String email,
@VerifyParam(required = true) String password,
@VerifyParam(required = true) String checkCode) {
try {
if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY))) {
throw new BusinessException("图片验证码不正确");
}
SessionWebUserDto sessionWebUserDto = userInfoService.login(email, password);
session.setAttribute(Constants.SESSION_KEY, sessionWebUserDto);
return getSuccessResponseVO(sessionWebUserDto);
} finally {
session.removeAttribute(Constants.CHECK_CODE_KEY);
}
}
登入功能不需要后端校验email格式,此次加入只是为了演示用法。
9.思路图:
内容有详有略,内容借鉴B站某网盘项目(项目挺不错,有兴趣可以去查阅)。个人理解加借鉴他人文章,有错误的地方可在评论区留言。