Spring AOP 和 AspectJ 详解

最近在看《Spring 实战》,说真的第四章《面向切面编程的Spring》讲的真心很烂,看了几遍都不清楚到底要表达什么,也没有讲清楚Spring AOP 和 AspectJ的区别关系,终于让我找到了一篇文章关于 Spring AOP (AspectJ) 你该知晓的一切,写的是真好,这里记录一下。

接下来举个我自己项目代码的例子。
我们知道,很多时候,要判断当前用户是否已经登录,也就是进行鉴权,登录后获取到用户的userId,方便进行后续操作,鉴权这里的逻辑,我们可以写一个Bean去处理,在每一个Controller的方法里用以下代码实现:

    @Autowired
    private UserService userService;
    public Object buy(HttpServletRequest request){
        Integer userId = userService.getUserId(request);
        if(userId == null){
            return "鉴权失败";
        }
        // 处理逻辑
        return "处理成功";
    }

这段代码看着没有什么问题,但是如果这类的逻辑多了,就要出现很多重复性的代码,满篇的重复逻辑,这个时候面向切面编程就要登场了。

这里我们通过环绕注解@Around("@annotation(xxxxx)的方式实现这个切面。
首先实现两个注解。

@CheckLogin

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface CheckLogin {
    /**
     * 设置为true会强制返回 ApiResponse 401
     *
     * @return 是否强制要求登录
     */
    boolean require() default true;
}

这个是一个标注方法的注解,标注的方法会调用鉴权逻辑进行鉴权,注解的参数require代码是否要求强制登录。

@UserId

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UserId {
}

这个是标注方法参数的注解,作用是如果鉴权成功,在切面编程的通知中会把标注的参数赋值为鉴权后的userId,比如:

@CheckLogin
public ApiResponse ling(@UserId Integer userId){
//处理逻辑
}

实现切面的通知

@Aspect
@Component
public class LoginAspect {

    // 鉴权客户端对象
    private final AuthClient authClient;
    // 鉴权失败时返回值
    private static final ApiResponse NOT_LOGIN_RESPONSE = ApiResponse.error(401,"您还未登录,请登录后再试");

    // 构造方法
    @Autowired
    public LoginAspect(AuthClient authClient) {
        this.authClient = authClient;
    }

    // 声明当前切面为环绕型切面,切入点为注解切入,注解CheckLogin标注的方法都进行环绕切入
    @Around("@annotation(com.cyf.annotation.CheckLogin)")
    public Object requireLogin(ProceedingJoinPoint pjp) throws Throwable {
        //获取切入的方法签名
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        //获取方法
        Method method = signature.getMethod();
        //获取注解实例
        CheckLogin checkLogin = method.getAnnotation(CheckLogin.class);
        Integer userId = null;
        try {
            //进行鉴权操作
            userId = authClient.getUserId();
        } catch (ThriftCallException e) {
            if (!(e instanceof AuthNoPassException)) {
                throw e;
            }
            //如果必须登录
            if (checkLogin.require()) {
                //如果返回值是ResponseEntity
                if (method.getReturnType().isAssignableFrom(ResponseEntity.class)) {
                    return ResponseEntity.ok(NOT_LOGIN_RESPONSE);
                } else {
                    return NOT_LOGIN_RESPONSE;
                }
            }
        }
        // 获取切点方法的参数
        Object[] args = pjp.getArgs();
        // 给切点的userId赋值
        assignUserId(method, args, userId);
        return pjp.proceed(args);
    }

    //给切点的@userId注解标注的方法赋值
    private void assignUserId(Method method, Object[] args, Integer userId){
        // 获取方法参数的所有注解
        Annotation[][] annotations = method.getParameterAnnotations();
        //获取方法参数的类型
        Class<?>[] types = method.getParameterTypes();
        for (int i = 0; i < annotations.length; i++) {
            Annotation[] arr = annotations[i];
            for (Annotation a : arr) {
                Class<? extends Annotation> c = a.annotationType();
                //判断是@UserId注解
                if (c.isAssignableFrom(UserId.class)) {
                    Class clazz = types[i];
                    if (clazz == Integer.class) {
                        args[i] = userId;//给参数赋值
                        return;
                    } else if (clazz == Optional.class) {
                        args[i] = Optional.ofNullable(userId);//给Optional参数赋值
                        return;
                    }
                    break;
                }
            }
        }
    }
}

使用切面

@RestController
@RequestMapping("test")
public class TestApi {
    @CheckLogin
    public ApiResponse buy(@UserId Integer userId){
        // 处理逻辑
        return "处理结果";
    }
}

是不是变得很简单,需要鉴权的部分只需要用@CheckLogin注解标注即可。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值