Spring 实现AOP常见的两种方式(注解或者自定义注解)

news2024/11/16 7:21:22

第一种

导入AOP相关坐标(依赖冲突解决办法,将依赖中版本号删除,springboot会自动匹配合适的版本 )

<dependencies>
    <!--spring核心依赖,会将spring-aop传递进来-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.2.10.RELEASE</version>
    </dependency>
    <!--切入点表达式依赖,目的是找到切入点方法,也就是找到要增强的方法-->
    <!--aspect: 切面, AOP;aspect-java -> aspectj-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.4</version>
    </dependency>
</dependencies>

切入点表达式标准格式:动作关键字(访问修饰符 返回值 包名。类/接口名.方法名(参数)异常名

execution(public User com.itheima.service.UserService.findById(int))

**- 动作关键字:描述切入点的行为动作,例如execution表示执行到指定切入点

  • 访问修饰符:public,private等,可以省略
  • 返回值
  • 包名
  • 类/接口名
  • 方法名
  • 参数
  • 异常名:方法定义中抛出指定异常,可以省略**

**- AOP切入点表达式

  • 可以使用通配符描述切入点,快速描述
  • :单个独立的任意符号,可以独立出现,也可以作为前缀或者后缀的匹配符出现*
execution(public * com.itheima.*.UserService.find*(*))

匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法。
… : 多个连续的任意符号,可以独立出现,常用语简化包名与参数的书写(参数一般使用…)

execution(public User com..UserService.findById(..))

匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法
+:专用于匹配子类类型

execution(* *..*Service+.*(..))
总结:
切入点表达式标准格式:动作关键字(访问修饰符 返回值  包名./接口名.方法名(参数)异常名)
* execution(* com.itheima.service.*Service.*(..))
切入点表达式描述通配符
作用:用于快速描述,范围描述
* : 匹配任意符号(常用)
.. : 匹配多个连续的任意符号(常用)
+ : 匹配子类类型

自定义注解的三个属性
1、@Target ElementType是一个枚举类型,它规范了注解的使用位置;

public enum ElementType {
    /** 类, 接口 (包括注解类型), 或 枚举 声明 */
    TYPE,
 
    /** 字段声明(包括枚举常量) */
    FIELD,
 
    /** 方法声明(Method declaration) */
    METHOD,
 
    /** 正式的参数声明 */
    PARAMETER,
 
    /** 构造函数声明 */
    CONSTRUCTOR,
 
    /** 局部变量声明 */
    LOCAL_VARIABLE,
 
    /** 注解类型声明 */
    ANNOTATION_TYPE,
 
    /** 包声明 */
    PACKAGE,
 
    /**
     * 类型参数声明
     *
     * @since 1.8
     */
    TYPE_PARAMETER,
 
    /**
     * 使用的类型
     *
     * @since 1.8
     */
    TYPE_USE
}

2、@Retention
RetentionPolicy这个枚举类型的常量描述保留注解的各种策略,它确定了注解的存活时间

public enum RetentionPolicy {
    /**
     * 注解只在源代码级别保留,编译时被忽略
     */
    SOURCE,
    /**
     * 注解将被编译器在类文件中记录
     * 但在运行时不需要JVM保留。这是默认的
     * 行为.
     */
    CLASS,
    /**
     *注解将被编译器记录在类文件中
     *在运行时保留VM,因此可以反读。
     * @see java.lang.reflect.AnnotatedElement
     */
    RUNTIME
}

3、@Documented

这个注解表明将会被javadoc记录,如果类型声明被这个注解了,它将成为公共API的一部分。

第二步:在启动类上开启aop注解开发
在这里插入图片描述
第三步: 定义一个切面类

package com.test.easycodeauto.aop;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;

import java.util.Arrays;

//通知类必须配置成Spring管理的bean
@Component
//设置当前类为切面类类
@Aspect
public class MyAdvice {
    //设置切入点,@Pointcut注解要求配置在方法上方  监控业务层所有的方法
    @Pointcut("execution(* com.test.easycodeauto.service.*Service.*(..))")
    private void pt(){}

    @Pointcut("execution(* com.test.easycodeauto.service.StudentService.queryById(..))")
    private void pt1(){}

    @Pointcut("execution(* com.test.easycodeauto.controller.StudentController.check*(..))")
    private void pt2() {
    }

    @Around("pt2()")
    public Object aroundCheck(ProceedingJoinPoint jpj) throws Throwable {
        // 去除web层的参数空格
        Object[] args = jpj.getArgs();
        System.out.println(Arrays.toString(args));
        for (int i = 0; i < args.length; i++) {
            if(args[i].getClass().equals(String.class)) {
                args[i] = args[i].toString().trim();
            }
        }
        Object proceed = jpj.proceed(args);
        return proceed;
    }

    /**
     * 通过 JoinPoint 来拿参数,around通知 使用ProceedingJoinPint
     */
//    @Before("pt1()")
    public void before(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("before advice...");
    }
//    @After("pt1()")
    public void after(JoinPoint jp) {
        Object[] args = jp.getArgs();
        System.out.println(Arrays.toString(args));
        System.out.println("after advide ...");
    }
//    @Around("pt1()")
    public Object around(ProceedingJoinPoint jpj) throws Throwable {
        Object[] args = jpj.getArgs();
        System.out.println(Arrays.toString(args));
        args[0] = "02";
        Object proceed = jpj.proceed(args);
        System.out.println("around advide ...");
        return proceed;

    }
//    @AfterReturning("pt1()")
    public void afterReturning() {

    }
//    @AfterThrowing("pt1()")
    public void afterThrowing() {

    }

    //设置在切入点pt()的前面运行当前操作(前置通知)
    @Around("pt()")
    public void method(ProceedingJoinPoint pjp) throws Throwable {
        // 获取执行签名信息
        Signature signature = pjp.getSignature();
        // 通过签名获取执行操作类型(接口名)
        String className = signature.getDeclaringTypeName();
        // 通过签名获取执行操作类型(方法名)
        String methodName = signature.getName();

        long start = System.currentTimeMillis();
        for (int i = 0; i < 100; i++) {
            pjp.proceed();
        }
        long end = System.currentTimeMillis();
        System.out.println("测试业务侧接口"+className +"."+ methodName +"万次执行效率:   "+ (end - start) + "ms");
    }
}

第二种:基于自定义注解开发

第一种方式:
自定义注解

package com.test.easycodeauto.customaop.aop;
import java.lang.annotation.*;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAop {
    String value() default "";
}

定义一个切面类

package com.test.easycodeauto.customaop.aop;

import com.test.easycodeauto.customaop.domain.Dept;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.HashMap;

@Component
@Aspect
@Slf4j
public class TestAopAspect {
    @Pointcut("@annotation(com.test.easycodeauto.customaop.aop.TestAop)")
    public void annotationPointcut(){}

    /**
     * 前置增强
     * @param jp
     */
//    @Before("annotationPointcut()")
    public void beforePointCut(JoinPoint jp) {
        // 此处进入方法前,可以实现一些业务逻辑
        log.info("before...");
    }

    /**
     * 后置增强
     * @param jp
     */
//    @After("annotationPointcut()")
    public void afterPointCut(JoinPoint jp) {
        // 此处进入方法,可以实现一些业务逻辑
        log.info("after...");
    }
    @Around("annotationPointcut()")
    public Object doAround(ProceedingJoinPoint jp) throws Throwable {
        log.info("around...");
        // 获取这个方法的注解的方法实例
        MethodSignature signature = (MethodSignature) jp.getSignature();
        Method method = signature.getMethod();

        // 获取这个注解的信息
        TestAop testAop = method.getAnnotation(TestAop.class);
        // 输出这个方法上这个注解的属性信息
        log.info(testAop.toString());
        // 获取传入的参数
        Object[] args = jp.getArgs();
        // 获取传入的参数名称,是有序的数组对象
        String[] argNames = signature.getParameterNames();
        // 这个把参数名称和参数匹对
        HashMap<String,Object> params = new HashMap<>();
        for (int i = 0; i < argNames.length; i++) {
            params.put(argNames[i],args[i]);
        }
        //通过使坏来改变参数一的值,即name的值
        args[0] = "666";
        // 通过反射的方式获取属性信息

        Object proceed = jp.proceed(args);
        // 通过使坏来改变运行结果
        proceed = "Helo Wo";
        return proceed;
    }
}

使用方式:

package com.test.easycodeauto.customaop.service.impl;

import com.test.easycodeauto.customaop.aop.TestAop;
import com.test.easycodeauto.customaop.domain.Dept;
import com.test.easycodeauto.customaop.service.DeptService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

@Service
@Slf4j
public class DeptServiceImpl implements DeptService {
    @Override
    @TestAop(value="TEST_AOP")
    public String testAop(String name, Dept dept) {
        log.info(name);
        log.info(dept.toString());
        log.info("testAop");
        return "testAop";
    }
}

第二种方式:
自定义一个注解:

package com.test.easycodeauto.customaop.aop;

import java.lang.annotation.*;

@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MyOperationLog {
    // 方法名称
    String methodName() default "";
    // 当前操作人
    String currentUser() default "";
    // 操作
    String operate() default "";
}

定义一个切面类:

package com.test.easycodeauto.customaop.aop;

import com.test.easycodeauto.customaop.domain.MyOperationLogVo;
import com.test.easycodeauto.customaop.service.LogService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Slf4j
@Component
@Aspect
public class MyLogAspect {

    @Autowired
    private LogService logService;

    // 定义一个切入点
    @Pointcut("@annotation(com.test.easycodeauto.customaop.aop.MyOperationLog)")
    public void pt(){}
    // 定义一个通知
    @Before("pt()")
    public void deBefore(JoinPoint joinPoint) {
        log.info("before...");
        // 获取自定义注解@MyOperationLog
        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        if (null == method) {
            return;
        }
        MyOperationLog myOperationLog = method.getAnnotation(MyOperationLog.class);

        // 获取签名
        String signa = joinPoint.getSignature().toString();
        // 获取方法名
        String methodName = signa.substring(signa.lastIndexOf(".") + 1, signa.indexOf("("));
        String methodName1 = joinPoint.getSignature().getName();//获取目标方法的名称;
        // 获取方法的execution
        String longTemp = joinPoint.getStaticPart().toLongString();
        //获取目标类的名称
        String classType = joinPoint.getTarget().getClass().getName();
        try{
            Class<?> clazz = Class.forName(classType);
            Method[] methods = clazz.getDeclaredMethods();
            for (Method method1 : methods) {
                if(method1.isAnnotationPresent(MyOperationLog.class)&& method1.getName().equals(methodName)) {
                    // 封装对象,可以进行一些业务逻辑
                    // 解析
                    MyOperationLogVo myOperationLogVos = parseAnnotation(method1);
                    // 日志添加
                    logService.addLog(myOperationLogVos);
                }
            }
        }catch (Exception e) {
           e.printStackTrace();
        }
    }

    private MyOperationLogVo parseAnnotation(Method method) {
        MyOperationLog myOperationLog = method.getAnnotation(MyOperationLog.class);
        if (null == myOperationLog) {
            return null;
        }
        MyOperationLogVo myOperationLogVo = new MyOperationLogVo();
        myOperationLogVo.setMethodName(myOperationLog.methodName());
        myOperationLogVo.setCurrentUser(myOperationLog.currentUser());
        myOperationLogVo.setOperate(myOperationLog.operate());
        return myOperationLogVo;
    }
}

使用方式:

 @RequestMapping("/controller")
 @MyOperationLog(methodName = "testLog",currentUser = "admin",operate = "查询")
    public String testLog() {
        return "log";
    }

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/657033.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

自动化测试必会之数据驱动测试

数据驱动测试 在实际的测试过程中&#xff0c;我们会发现好几组用例都是相同的操作步骤&#xff0c;只是测试数据的不同&#xff0c;而我们往往需要编写多次用例来进行测试&#xff0c;此时我们可以利用数据驱动测试来简化该种操作。 参数化&#xff1a; 输入数据的不同从而产…

C语言:输入两个升序排列的序列,将两个序列合并为一个有序序列并输出。

题目&#xff1a; 描述 输入两个升序排列的序列&#xff0c;将两个序列合并为一个有序序列并输出。 输入描述&#xff1a; 输入包含三行&#xff0c; 第一行包含两个正整数n, m&#xff0c;用空格分隔。n表示第二行第一个升序序列中数字的个数&#xff0c;m表示第三…

C++ 教程(12)——循环

C 循环 有的时候&#xff0c;可能需要多次执行同一块代码。一般情况下&#xff0c;语句是顺序执行的&#xff1a;函数中的第一个语句先执行&#xff0c;接着是第二个语句&#xff0c;依此类推。 编程语言提供了允许更为复杂的执行路径的多种控制结构。 循环语句允许我们多次…

朋友圈功能合集来咯!定时发朋友圈,查看朋友圈,朋友圈跟圈,一键转发朋友圈,延迟评论

&#x1f30a;发布朋友圈 功能介绍&#xff1a;可使用已登录在系统上的微信号发送朋友圈。支持发送图片、文字、视频和公众号链接等几种类型的内容。 &#xff08;1)朋友圈内容编辑&#xff1a;可以在输入框中输入要发送的文本&#xff0c;并在浮窗中选择表情。上传图片可以点…

【c++11】c++11特性

c11 c11简介列表初始化std::initializer_list autodecltypenullptr 结语 c11简介 从C0x到C11&#xff0c;C标准10年磨一剑&#xff0c;第二个真正意义上的标准珊珊来迟。相比于C98/03&#xff0c;C11则带来了数量可观的变化&#xff0c;其中包含了约140个新特性&#xff0c;以…

QT QTreeView\QTreeWidget控件 使用详解

本文详细的介绍了QTreeView、QTreeWidget控件的各种操作&#xff0c;例如&#xff1a;新建界面、QTreeWidget、QTreeView、控件布局、设置列、设置宽高、设置列表头、设置复选框、设置图标、添加树、删除树、查找树、修改树、设置选中、树排序、事件、信号、槽函数、添加节点、…

【玩转Docker小鲸鱼叭】MacOS系统安装Docker

安装Docker Mac 系统安装 Docker 其实很简单&#xff0c;我们在官方文档下载安装一下就可以了&#xff0c;但是需要注意 Docker 官方建议 MacOS 必须是版本 11 或更高版本&#xff0c;如果版本较低&#xff0c;建议先升级 MacOS 版本。 可以通过左上角的小  图片查看系统版…

浅析Spring-kafka源码——消费者模型的实现

SpringBoot项目中的消费端实现而言,Spring-kafka没有用原生的ConsumerConnector,,而是借助原生client的拉取消息功能做了自己的消费模型的实现,提供了@KafkaListener注解这种方式实现消费。 开发中在使用Spring-kafka时,一般也都是通过使用@KafkaListener注解的方法来实现…

Android MPAndroidChart折线图渐变填充实现

效果如下&#xff1a; 以下是一个从上到下渐变的drawable&#xff0c;上面是蓝色&#xff0c;逐步向下变成白色&#xff1a; chart_bg.xml <?xml version"1.0" encoding"utf-8"?> <shape xmlns:android"http://schemas.android.com/apk…

静态链接和动态链接 -- 静态加载(隐式调用)和动态加载(显式调用)

区别 静态链接和动态链接 静态链接 : 由链接器在链接时将库的内容加入到可执行程序中&#xff0c;这里的库是静态库&#xff0c;Windows下是*.lib后缀&#xff0c;Linux下是*.a后缀。动态链接 : 可执行程序加载时(静态加载) 或者 运行时(动态加载)&#xff0c;将库文件中的内容…

OpenShift Virtualization - 通过外部固定 IP 访问 VM 中的服务(附视频)

《OpenShift / RHEL / DevSecOps 汇总目录》 说明&#xff1a;本文已经在 OpenShift 4.12 的环境中验证 文章目录 方法1&#xff1a;通过 Service 的 NodePort 访问 VM方法2&#xff1a;通过外部 IP 访问 VM确认 OpenShift 集群环境为 Worker 节点添加 Linux Bridge创建使用 Li…

『DevOps最佳实践』使用Jenkins和Harbor进行持续集成和交付的解决方案

&#x1f4e3;读完这篇文章里你能收获到 全文采用图文形式讲解学会使用Harbor配置项目学会在Jenkins中配置Harbor推送权限使用Jenkins和Harbor进行持续集成的实践感谢点赞收藏&#xff0c;避免下次找不到~ 文章目录 一、准备工作1. 环境准备2. 修改Docker配置文件3. Docker登陆…

2023蓝桥杯大学A组C++国赛游记+个人题解

Day0 发烧了一晚上没睡着&#xff0c;感觉鼻子被打火机烧烤一样难受&#xff0c;心情烦躁 早上6点起来吃了个早饭&#xff0c;思考能力完全丧失了&#xff0c;开始看此花亭奇谭 看了六集&#xff0c;准备复习数据结构考试&#xff0c;然后秒睡 一睁眼就是下午2点了 挂了个…

04- c语言数组 (C语言)

一 数组的概念 1、在程序设计中&#xff0c;为了方便处理数据把具有相同类型的若干变量按有序形式组织起来,这些按序排列的 同类数据元素的集合 称为 数组。 2、在C语言中&#xff0c;数组属于构造数据类型。一个数组可以分解为多个数组元素&#xff0c;这些数组元素可以是基本…

力扣动态规划专题(三)完全背包 518.零钱兑换II 377. 组合总和 Ⅳ 70. 爬楼梯 322. 零钱兑换 279.完全平方数 步骤及C++实现

文章目录 完全背包一维dp数组 滚动数组 518.零钱兑换II377. 组合总和 Ⅳ70. 爬楼梯322. 零钱兑换279.完全平方数139.单词拆分 完全背包 完全背包的物品数量是无限的&#xff0c;01背包的物品数量只有一个 完全背包和01背包分许步骤一样&#xff0c;唯一不同就是体现在遍历顺序上…

deque的介绍

前言 为什么会存在deque呢&#xff1f;在c标准库中deque是作为 stack和queue的底层容器就是deque&#xff0c;我们要是了解过list和vector就会知道这两种容器各有优劣&#xff0c;vector的优点是支持随机访问&#xff0c;进而可以支持排序和二分查找等算法&#xff0c;它的缺点…

鼠标事件 获取鼠标坐标及点击事件

运行效果&#xff1a; 头文件 #ifndef MOUSEEVENT_H #define MOUSEEVENT_H #include #include #include #include class MouseEvent : public QMainWindow { Q_OBJECT public: MouseEvent(QWidget *parent 0); ~MouseEvent(); protected: void mousePressEvent(QMouse…

Linux:一键搭建ftp服务(vsftpd)

《TRO-23614-VSFTPD》 如果csdn收费 可以和我si liao 我会免费发给你 我发的这个是一个tar归档包&#xff0c;脚本就在其中 Linux&#xff1a;《tar》归档命令_鲍海超-GNUBHCkalitarro的博客-CSDN博客 tar xfz tarro_vsftpd.tar.gz -C /root/ # tar xfz tar包路径 -…

SpringBoot第27讲:SpringBoot集成MySQL - MyBatis 多个数据源

SpringBoot第27讲&#xff1a;SpringBoot集成MySQL - MyBatis 多个数据源 本文是SpringBoot第27讲&#xff0c;在某些场景下&#xff0c;Springboot需要使用多个数据源&#xff0c;以及某些场景会需要多个数据源的动态切换。本文主要介绍上述场景及 SpringBootMyBatis实现多个数…

【期末满分作业】C语言程序设计 实训1——奖学金评定系统的设计与实现(附带实验报告、源码以及解释)

大家好&#xff0c;各位努力奋斗的大学生小伙伴们&#xff01;今天&#xff0c;我将带你们领略一项令人惊叹的程序设计奇迹——《奖学金评定系统》&#xff01;是不是感到激动呢&#xff1f;别急&#xff0c;让我为你们揭开这个能让你在C语言程序设计中拿满分的秘密武器&#x…