SpringAOP+自定义注解简单使用

一、SpringAOP简述


SpringAOP可以帮助我们在不修改源代码的前提下实现功能增强,其底层实现原理基于Java动态代理或者CGLIB。

在这里插入图片描述

之前我们使用 execution表达式指定被AOP增强的方法:(execution关键字用于描述哪些方法需要切面逻辑)

在这里插入图片描述

但是这样使用非常不灵活,因为并不是Service中所有的方法都需要被增强。

其实我们可以参考Spring声明式事务注解@Transactional,在项目中利用自定义注解实现了大量共性需求。


SpringAOP+自定义注解的应用场景:

  • 收集上报指定关键方法的入参、执行时间、返回结果等关键信息,用作后期调优处理;
  • 关键方法在幂等性前置校验(基于本地消息表);
  • 类似于Spring-Retryt模块,提供关键方法多次调用重试机制;
  • 提供关键方法自定义的快速熔断、服务降级等职责;
  • 关键方法的共性入参校验、权限校验;
  • 关键方法在执行后的扩展行为,例如记录日志、启动其他任务等;

二、SpringAOP+自定义注解使用


1、创建springboot工程,在pom.xml中引入aspectjweaver依赖

<!-- AOP切面编程框架 -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <!--<version>1.9.4</version>-->
</dependency>

2、编写自定义注解(使用@interface关键字定义注解)

package cn.baidou.dianping.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定义方法注解
 */
// @Target(ElementType.METHOD) 用来约束这个自定义注解只能用在方法上
@Target(ElementType.METHOD)
// @Retention 用来控制注解的生命周期,RUNTIME表示这个注解一直存活 (作用在源码阶段,字节码文件阶段,运行阶段)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodExporter {

}

3、编写切面类

package cn.baidou.dianping.aop;

import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * 获取目标入参、执行时间、执行过程、返回结果等细节并打印到日志上
 *
 * @author 白豆五
 * @version 2023/06/15
 * @since JDK8
 */
@Aspect //设置当前类为切面类
@Component //配置成Spring管理的bean
@Slf4j
public class MethodExporterAspect {
    
    /*
        @Around:环绕通知,最强大的通知类型,可以控制方法入参、执行、返回结果等各方面细节
        "@annotation(xxx.MethodExporter)":表示任何添加@MethodExporter注解的目标方法都将在执行方法前先执行该切面方法
     */
    @Around("@annotation(cn.baidou.dianping.annotation.MethodExporter)")
    public Object methodExporter(ProceedingJoinPoint joinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();//开始时间
        Object proceed = joinPoint.proceed();      // 执行目标方法,获取方法返回值
        long endTime = System.currentTimeMillis(); //结束时间

        ObjectMapper mapper = new ObjectMapper();

        // 将入参JSON序列化
        String jsonParam = mapper.writeValueAsString(joinPoint.getArgs());//joinPoint.getArgs()获取目标方法的参数

        // 将返回结果JSON序列化
        String jsonResult = null;
        if (jsonResult != null) {
            jsonResult = mapper.writeValueAsString(proceed);//mapper.writeValueAsString()可用于将任何Java值序列化为字符串
        } else {
            jsonResult = "null";
        }

        // 模拟上报过程
        log.debug("正在上报服务器调用过程:\ntarget:{}.{}()\nexecution:{}ms,\nparameter:{}\nresult:{}",
                joinPoint.getTarget().getClass().getSimpleName(),
                joinPoint.getSignature().getName(),
                (endTime - startTime),
                jsonParam,
                jsonResult);

        return proceed;
    }
}

4、编写测试代码

package cn.baidou.dianping.controller;

import cn.baidou.dianping.annotation.MethodExporter;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.TimeUnit;

/**
 * @author 白豆五
 * @version 2023/06/15
 * @since JDK8
 */
@RestController
@RequestMapping("/test")
public class TestController {

    @MethodExporter
    @GetMapping("/list")
    public Map test() {
        Map resultMap = new LinkedHashMap();
        resultMap.put("code", "200");
        resultMap.put("message", "ok");

        try {
            TimeUnit.SECONDS.sleep(new Random().nextInt(10));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return resultMap;
    }
}

5、重启项目,测试接口

在这里插入图片描述

控制台输出:

在这里插入图片描述

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
首先,我们需要定义一个自定义注解 `@RequiresPermissions`,用于标识需要授权访问的方法,例如: ```java @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface RequiresPermissions { String[] value(); // 权限值 } ``` 然后,我们需要实现一个切面,用于拦截被 `@RequiresPermissions` 标识的方法,并进行权限校验,例如: ```java @Component @Aspect public class PermissionCheckAspect { @Autowired private AuthService authService; @Around("@annotation(requiresPermissions)") public Object checkPermission(ProceedingJoinPoint joinPoint, RequiresPermissions requiresPermissions) throws Throwable { // 获取当前用户 User user = authService.getCurrentUser(); if (user == null) { throw new UnauthorizedException("用户未登录"); } // 获取当前用户的权限列表 List<String> permissions = authService.getUserPermissions(user); // 校验权限 for (String permission : requiresPermissions.value()) { if (!permissions.contains(permission)) { throw new ForbiddenException("没有访问权限:" + permission); } } // 执行目标方法 return joinPoint.proceed(); } } ``` 在切面中,我们首先通过 `AuthService` 获取当前用户及其权限列表,然后校验当前用户是否拥有被 `@RequiresPermissions` 标识的方法所需的所有权限,如果没有则抛出 `ForbiddenException` 异常,如果有则继续执行目标方法。 最后,我们需要在 Spring 配置文件中启用 AOP 自动代理,并扫描切面所在的包,例如: ```xml <aop:aspectj-autoproxy /> <context:component-scan base-package="com.example.aspect" /> ``` 这样,我们就通过 Spring AOP自定义注解模拟实现了类似 Shiro 权限校验的功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

白豆五

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值