AOP能做什么?答:参数的校验 怎么实现AOP呢?答:就写一个注解,在相应的方法上加注解…… 怎么实现AOP呢?
1.导包
代码语言:xml复制 <dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.4</version>
</dependency>
版本根据springboot的版本自己决定,我用的boot2.6.1,所以aop就用的1.9.4
这个包不是spring的,也不是jdk的
实现切面有很多中方式,最常见的就是定义一个注解
2.定义注解
先来看这样两个方法:
代码语言:java复制 public void checkCode(HttpServletResponse response, HttpSession session, Integer type) throws IOException {
CreateImageCode vCode = new CreateImageCode(130, 38, 5, 10);
response.setHeader("Pragma", "no-cache");
response.setHeader("Cache-Control", "no-cache");
response.setDateHeader("Expires", 0);
response.setContentType("image/jpeg");
String code = vCode.getCode();
if (type == null || type == 0) {
session.setAttribute(Constants.CHECK_CODE_KEY, code);
} else {
session.setAttribute(Constants.CHECK_CODE_KEY_EMAIL, code);
}
vCode.write(response.getOutputStream());
}
代码语言:java复制public ResponseVO sendEmailCode(HttpSession session, String email, String checkCode, Integer type) {
try {
//解决空指针异常,需要判断
//使用AOP做参数校验
if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY_EMAIL))) {
throw new BusinessException("图片验证码不正确");
}
emailCodeService.sendEmailCode(email, type);
return getSuccessResponseVO(null);
} finally {
//每次用完这个验证码,不管成功或者失败,都要重置
session.removeAttribute(Constants.CHECK_CODE_KEY_EMAIL);
}
}
很显然,response和session不为空,可能传null的只有type,而方法中对type设置了默认值。所以,checkCode方法不需要参数校验。
相反,sendEmailCode方法中没有默认值就必须进行校验。
所以需要定义一个注解进行参数校验
代码语言:java复制@Target({ElementType.METHOD}) //定义在方法上
@Retention(RetentionPolicy.RUNTIME) //在运行时保留
@Documented
@Mapping
public @interface GlobalInterceptor {
/**
* 校验参数
* 默认不校验参数
* @return
*/
boolean checkParams() default false;
}
- 因为要在方法上使用,所以要加注解
@Target({ElementType.METHOD})
- @Retention是一个Java元注解,他的官方解释是:用于指定另一个注解的保留策略。元注解是用于注解其他注解的特殊注解。
- 我们只要知道他是干什么的就行了。通俗理解就是让我们定义的这个注解什么时候保留,保留策略一共有三
RetentionPolicy.SOURCE
:注解只在源码中保留,编译时会被丢弃,不会保留在.class
文件中。RetentionPolicy.CLASS
:注解在编译时保留在.class
文件中,但在运行时不可见。默认保留策略。RetentionPolicy.RUNTIME
:注解保留在.class
文件中,并且在运行时可以通过反射机制读取。
- 我们只要知道他是干什么的就行了。通俗理解就是让我们定义的这个注解什么时候保留,保留策略一共有三
@Documented
注解就可加可不加了- 如果说咱哥们任性,我不加
@Mapping
注解,那你写这个GlobalInterceptor没什么用。
这里面,虽然定义了校验参数,但是参数里面还有相应的特性,我们还要定义一个注解。
代码语言:java复制@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; // 正则表达式枚举
}
这样,注解就都定义好了,每个参数默认都是不校验的。
那么,注解定义完了,要怎么实现切面呢?
3.定义一个类
GlobalOperatcionAspect
全局操作拦截
@Aspect
@Component("globalOperatcionAspect")
public class GlobalOperatcionAspect {
}
在代码中,要用@Aspect
注解来证明它是一个切面,用@Component
注解交给spring管理
下面我就直接上完整代码了
代码语言:java复制@Aspect
@Component("globalOperatcionAspect")
@Slf4j
public class GlobalOperatcionAspect {
private static final String TYPE_STRING = "java.lang.String";
private static final String TYPE_INTEGER = "java.lang.Integer";
private static final String TYPE_LONG = "java.lang.Long";
@Autowired
private UserInfoService userInfoService;
@Autowired
private AppConfig appConfig;
@Pointcut("@annotation(这里换成你GlobalInterceptor的路径)") //定义切点
private void requestInterceptor(){ //请求拦截切点
}
//事件通知before、after、around
@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) {
log.error("全局拦截器异常", e);
throw e;
} catch (Exception e) {
log.error("全局拦截器异常", e);
throw new BusinessException(ResponseCodeEnum.CODE_500);
} catch (Throwable e) {
log.error("全局拦截器异常", e);
throw new BusinessException(ResponseCodeEnum.CODE_500);
}
}
//校验登录
private void checkLogin(Boolean checkAdmin) {
//SpringBoot在一个类中拿到session信息
HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
HttpSession session = request.getSession();
//从session中获取登录用户信息
SessionWebUserDto sessionUser = (SessionWebUserDto) session.getAttribute(Constants.SESSION_KEY);
if (sessionUser == null && appConfig.getDev() != null && appConfig.getDev()) {
List<UserInfo> userInfoList = userInfoService.findListByParam(new UserInfoQuery());
if (!userInfoList.isEmpty()) {
UserInfo userInfo = userInfoList.get(0);
sessionUser = new SessionWebUserDto();
sessionUser.setUserId(userInfo.getUserId());
sessionUser.setNickName(userInfo.getNickName());
sessionUser.setAdmin(true);
session.setAttribute(Constants.SESSION_KEY, sessionUser);
}
}
if (null == sessionUser) {
throw new BusinessException(ResponseCodeEnum.CODE_901);
}
if (checkAdmin && !sessionUser.getAdmin()) {
throw new BusinessException(ResponseCodeEnum.CODE_404);
}
}
/**
* 校验参数
*
* @param m
* @param arguments
* @throws BusinessException
*/
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);
}
}
}
private void checkObjValue(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) {
log.error("校验参数失败", e);
throw e;
} catch (Exception e) {
log.error("校验参数失败", e);
throw new BusinessException(ResponseCodeEnum.CODE_600);
}
}
/**
* 校验参数
*
* @param value
* @param verifyParam
* @throws BusinessException
*/
private void checkValue(Object value, VerifyParam verifyParam) throws BusinessException {
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);
}
}
}
回到sendEmailCode
方法里,我们就可以加入相应注解进行校验了
@GlobalInterceptor(checkParams = true, checkLogin = false)
public ResponseVO sendEmailCode(HttpSession session,
@VerifyParam(required = true, regex = VerifyRegexEnum.EMAIL, max = 150) String email,
@VerifyParam(required = true) String checkCode,
@VerifyParam(required = true) Integer type) {
try {
//解决空指针异常,需要判断
//使用AOP做参数校验
if (!checkCode.equalsIgnoreCase((String) session.getAttribute(Constants.CHECK_CODE_KEY_EMAIL))) {
throw new BusinessException("图片验证码不正确");
}
emailCodeService.sendEmailCode(email, type);
return getSuccessResponseVO(null);
} finally {
//每次用完这个验证码,不管成功或者失败,都要重置
session.removeAttribute(Constants.CHECK_CODE_KEY_EMAIL);
}
}