AOP 使用
持续更新中… …
切面编程,适用场景:
- 权限校验
- 记录日志
引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
创建切面类
@Aspect
@Component
@Slf4j
public class LogInterceptor {
}
- 使用
@Aspect
声明这是一个切面类 - 注册为
Bean
方便 spring 管理
定义切入点
@Aspect
@Component
@Slf4j
public class LogInterceptor {
/**
* 执行拦截
*/
@Around("execution(* com.yannqing.yanoj.controller.*.*(..))")
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
}
}
- @Around 进行定义切入点:环绕通知,可以自定义对应位置的前后逻辑
execution(* com.yannqing.yanoj.controller.*.*(..))
: 定义了切入点,即拦截所有在com.yannqing.yanoj.controller
包下的所有方法。- execution 表达式:
*
: 表示任意返回类型com.yannqing.yanoj.controller.*.*
: 表示匹配com.yannqing.yanoj.controller
包下的所有类的所有方法。(..)
: 表示匹配任意数量和类型的参数。
ProceedingJoinPoint point
: 代表了被拦截的方法,可以通过它获取方法的参数和执行方法。
对注解进行拦截:
@Aspect
@Component
public class AuthInterceptor {
/**
* 执行拦截
*/
@Around("@annotation(authCheck)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
//... 其他逻辑
authCheck.musRole();
// 执行原方法
return joinPoint.proceed();
}
}
- @annotation 指定注解
- @AuthCheck 为自定义注解
- 可以执行注解内的方法
切入点相关注解
@Around
用于定义环绕通知,并且需要有一个返回值和
ProceedingJoinPoint
参数。用于定义环绕通知,并且需要有一个返回值和ProceedingJoinPoint
参数。
@Pointcut
作用:标注的方法通常都是空参,空方法体,方法名随意定义即可,只是用于标注切入点,而使用@Around,@Before等方法切入
@Pointcut("@annotation(org.springframework.web.bind.annotation.RequestMapping)")
public void requestMappingMethods() {
// 这个方法没有实现,主要用于标识切入点
}
@Around("requestMappingMethods()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// 在 RequestMapping 注解的方法执行前后执行的逻辑
return joinPoint.proceed();
}
定义执行逻辑
@Aspect
@Component
@Slf4j
public class LogInterceptor {
/**
* 执行拦截
*/
@Around("execution(* com.yannqing.yanoj.controller.*.*(..))")
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
// ... 原方法执行前的逻辑
//...
// 执行原方法
Object result = point.proceed();
//...
// ... 原方法执行后的逻辑
return result;
}
}
使用示例
请求响应日志 AOP
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* 请求响应日志 AOP
*
**/
@Aspect
@Component
@Slf4j
public class LogInterceptor {
/**
* 执行拦截
*/
@Around("execution(* com.yannqing.yanoj.controller.*.*(..))")
public Object doInterceptor(ProceedingJoinPoint point) throws Throwable {
// 计时
StopWatch stopWatch = new StopWatch();
stopWatch.start();
// 获取请求路径
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest httpServletRequest = ((ServletRequestAttributes) requestAttributes).getRequest();
// 生成请求唯一 id
String requestId = UUID.randomUUID().toString();
String url = httpServletRequest.getRequestURI();
// 获取请求参数
Object[] args = point.getArgs();
String reqParam = "[" + StringUtils.join(args, ", ") + "]";
// 输出请求日志
log.info("request start,id: {}, path: {}, ip: {}, params: {}", requestId, url,
httpServletRequest.getRemoteHost(), reqParam);
// 执行原方法
Object result = point.proceed();
// 输出响应日志
stopWatch.stop();
long totalTimeMillis = stopWatch.getTotalTimeMillis();
log.info("request end, id: {}, cost: {}ms", requestId, totalTimeMillis);
return result;
}
}
权限校验 AOP
import com.yannqing.yanoj.common.ErrorCode;
import com.yannqing.yanoj.model.entity.User;
import com.yannqing.yanoj.model.enums.UserRoleEnum;
import com.yannqing.yanoj.annotation.AuthCheck;
import com.yannqing.yanoj.exception.BusinessException;
import com.yannqing.yanoj.service.UserService;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
/**
* 权限校验 AOP
*
*/
@Aspect
@Component
public class AuthInterceptor {
@Resource
private UserService userService;
/**
* 执行拦截
*
* @param joinPoint
* @param authCheck
* @return
*/
@Around("@annotation(authCheck)")
public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
String mustRole = authCheck.mustRole();
RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
// 当前登录用户
User loginUser = userService.getLoginUser(request);
UserRoleEnum mustRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
// 不需要权限,放行
if (mustRoleEnum == null) {
return joinPoint.proceed();
}
// 必须有该权限才通过
UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByValue(loginUser.getUserRole());
if (userRoleEnum == null) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 如果被封号,直接拒绝
if (UserRoleEnum.BAN.equals(userRoleEnum)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
// 必须有管理员权限
if (UserRoleEnum.ADMIN.equals(mustRoleEnum)) {
// 用户没有管理员权限,拒绝
if (!UserRoleEnum.ADMIN.equals(userRoleEnum)) {
throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
}
}
// 通过权限校验,放行
return joinPoint.proceed();
}
}