AOP 使用

2024 年 9 月 22 日 星期日(已编辑)
/ ,
11
摘要
AOP 使用
这篇文章上次修改于 2024 年 9 月 22 日 星期日,可能部分内容已经不适用,如有疑问可询问作者。

AOP 使用

持续更新中… …

切面编程,适用场景:

  1. 权限校验
  2. 记录日志

引入依赖

<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();
    }
}
  • Loading...
  • Loading...
  • Loading...
  • Loading...
  • Loading...