SpringAop是什么?

简单介绍:

AOP:Aspect Oriented Programming (面向切面编程、面向方面编程),其实就是面向特定方法编程。

场景:

比如现在有一个需求,我要统计每一个业务方法的耗时时长,

我们只需在业务方法的前面获取一个开始时间,在方法的后面再获取一个结束时间,然后将两时间相减就能得出耗时时长。

这样做当然没问题,但是在一个项目中,业务方法是很多的,如果每次都这样操作,那工作量是非常大的,我们需要换一种方式实现。

我们就可以用到springAop技术

核心:在不惊动原始代码的基础上增强功能。

我们需要统计业务方法的耗时,可以创建一个模板方法,将记录耗时时长的公共的代码放入模板方法。

                                     

原始方法就是业务方法,面向这种特定方法的编程就是面向切面编程。

这个模板方法中定义的逻辑就是创建出来的代理对象方法的逻辑。

实现:

动态代理是面向切面编程最主流的实现。而SpringAOP是Spring框架的高级技术,旨在管理bean对象的过程中,主要通过底层的动态代理机制,对特定的方法进行编程。

编写AOP程序:

针对特定方法根据业务需要进行编程

我们需要创建一个切面类,在类中定义模板方法,并把类交给spring管理

@Component
@Aspect //当前类为切面类
@Slf4j
public class TimeAspect {

    @Around("execution(* com.itheima.service.*.*(..))") 
    public Object recordTime(ProceedingJoinPoint pjp) throws Throwable {
        //记录方法执行开始时间
        long begin = System.currentTimeMillis();

        //执行原始方法
        Object result = pjp.proceed();

        //记录方法执行结束时间
        long end = System.currentTimeMillis();

        //计算方法执行耗时
        log.info(pjp.getSignature()+"执行耗时: {}毫秒",end-begin);

        return result;
    }
}

pjp.proceed() 执行原始方法,原始方法可能有返回值,所以最后要把返回值retrun。通过pjp.getSignature() 可以获取原始方法的名称,这样就可以具体知道是哪个方法耗时的时间。

在@Around注解中有个excution表达式,表示对哪些方法进行增强,* com.itheima.service.*.*(..),

* 表示方法的返回值  ,后面的是包名,在任意接口的任意方法上执行。(..)表示方法的形参也是任意的。


AOP核心概念:

①连接点:JointPoint ,可以被AOP控制的方法(暗含方法执行时的相关信息)。

②通知:Advice,指那些重复的逻辑,也就是共性功能(最终体现为一个方法)。

③切入点:PointCut,匹配连接点的条件,通知仅会在切入点方法执行时被应用。

④切面:Aspect,描述通知与切入点的对应关系(通知+切入点)

⑤目标对象:Target,通知所应用的对象

AOP执行流程:

定义好切入点表达式后,在程序运行时,SpringAOP会自动地基于动态代理技术为目标对象生成一个对应的代理对象,也就是上图的DeptServiceProxy对象, 在对象中已经做了对业务方法的增强。此时,再进行service注入的时候,spring就不会注入原始的目标对象,而是注入生成出来的代理对象,此代理对象已经对业务方法做了增强的处理。


AOP进阶:
Spring中AOP的通知类型:
  • @Around:环绕通知,此注解标注的通知方法在目标方法前、后都被执行

  • @Before:前置通知,此注解标注的通知方法在目标方法前被执行

  • @After :后置通知,此注解标注的通知方法在目标方法后被执行,无论是否有异常都会执行

  • @AfterReturning : 返回后通知,此注解标注的通知方法在目标方法后被执行,有异常不会执行

  • @AfterThrowing : 异常后通知,此注解标注的通知方法发生异常后执行

注意:

① :@Around环绕通知需要自己调用ProceedingJoinPoint.proceed() 来让原始方法执行,其它通知不需要考虑目标方法执行。

②:@Aound环绕通知方法的返回值,必须指定为Object,来接收原始方法的返回值。

五种通知类型代码演示:
@Slf4j
@Component
@Aspect
public class MyAspect1 {

    //切入点方法(公共的切入点表达式)
    @Pointcut("execution(* com.itheima.service.*.*(..))")
    private void pt(){

    }

    //前置通知(引用切入点)
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info("before ...");

    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        log.info("around before ...");

        //调用目标对象的原始方法执行
        Object result = proceedingJoinPoint.proceed();
        //原始方法在执行时:发生异常
        //后续代码不在执行

        log.info("around after ...");
        return result;
    }

    //后置通知
    @After("pt()")
    public void after(JoinPoint joinPoint){
        log.info("after ...");
    }

    //返回后通知(程序在正常执行的情况下,会执行的后置通知)
    @AfterReturning("pt()")
    public void afterReturning(JoinPoint joinPoint){
        log.info("afterReturning ...");
    }

    //异常通知(程序在出现异常的情况下,执行的后置通知)
    @AfterThrowing("pt()")
    public void afterThrowing(JoinPoint joinPoint){
        log.info("afterThrowing ...");
    }
}

通知顺序:

默认按照切面类的类名字母排序:

  • 目标方法前的通知方法:字母排名靠前的先执行

  • 目标方法后的通知方法:字母排名靠前的后执行

一般我们通过@Order注解可以控制通知的执行顺序

@Slf4j
@Component
@Aspect
@Order(2)  //切面类的执行顺序(前置通知:数字越小先执行; 后置通知:数字越小越后执行)
public class MyAspect2 {
    //前置通知
    @Before("execution(* com.itheima.service.*.*(..))")
    public void before(){
        log.info("MyAspect2 -> before ...");
    }

    //后置通知 
    @After("execution(* com.itheima.service.*.*(..))")
    public void after(){
        log.info("MyAspect2 -> after ...");
    }
}
切入点表达式:

作用:主要用来决定项目中的哪些方法需要加入通知。

它的常见形式有两种:

①execution(……):

根据方法的签名来匹配

execution(访问修饰符?  返回值  包名.类名.?方法名(方法参数) throws 异常?)

其中带?的表示可以省略的部分

  • 访问修饰符:可省略(比如: public、protected)

  • 包名.类名: 可省略

  • throws 异常:可省略(注意是方法上声明抛出的异常,不是实际抛出的异常)

示例:

@Before("execution(void com.itheima.service.impl.DeptServiceImpl.delete(java.lang.Integer))")

②@annotation(……) :

根据注解匹配

自定义注解:Log           一般我们记录操作日志需要用到  自定义注解 + 环绕通知

//自定义注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    public String title() ;								// 模块名称
    public OperatorType operatorType() default OperatorType.MANAGE;	// 操作人类别
    public int businessType() ;     // 业务类型(0其它 1新增 2修改 3删除)
    public boolean isSaveRequestData() default true;   // 是否保存请求的参数
    public boolean isSaveResponseData() default true;  // 是否保存响应的参数
}

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Import(value = LogAspect.class)            // 通过Import注解导入日志切面类到Spring容器中
public @interface EnableLogAspect {  //这个注解可以添加到启动类上,程序启动时可以扫描到LogAspect这个切面类
}


@Aspect
@Component
@Slf4j
public class LogAspect {   //环绕通知切面类定义

    @Autowired
    private AsyncOperLogService asyncOperLogService;

    @Around("@annotation(sysLog)")
    public Object doAroundAdvice(ProceedingJoinPoint joinPoint, Log sysLog){
        // 构建前置参数
        SysOperLog sysOperLog = new SysOperLog() ;

        LogUtil.beforeHandleLog(sysLog, joinPoint, sysOperLog);  //封装日志信息
        Object proceed = null;
        try {
            proceed = joinPoint.proceed();              // 执行业务方法
            LogUtil.afterHandlLog(sysLog,proceed, sysOperLog, 0, null);   //封装日志信息
        } catch (Throwable e) {      // 代码执行进入到catch中,业务方法执行产生异常
            e.printStackTrace();
            LogUtil.afterHandlLog(sysLog,proceed, sysOperLog, 1, e.getMessage()); //封装日志信息
            throw new RuntimeException();
        }
        // 保存日志数据
        asyncOperLogService.saveSysOperLog(sysOperLog);
        return proceed ;
    }
}

连接点:

在Spring中用JoinPoint抽象了连接点,用它可以获得方法执行时的相关信息,如目标类名、方法名、方法参数等。

  • 对于@Around通知,获取连接点信息只能使用ProceedingJoinPoint类型

  • 对于其他四种通知,获取连接点信息只能使用JoinPoint,它是ProceedingJoinPoint的父类型

@Slf4j
@Component
@Aspect
public class MyAspect7 {

    @Pointcut("@annotation(com.itheima.anno.MyLog)")
    private void pt(){}
   
    //前置通知
    @Before("pt()")
    public void before(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> before ...");
    }
    
    //后置通知
    @Before("pt()")
    public void after(JoinPoint joinPoint){
        log.info(joinPoint.getSignature().getName() + " MyAspect7 -> after ...");
    }

    //环绕通知
    @Around("pt()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //获取目标类名
        String name = pjp.getTarget().getClass().getName();
        log.info("目标类名:{}",name);

        //目标方法名
        String methodName = pjp.getSignature().getName();
        log.info("目标方法名:{}",methodName);

        //获取方法执行时需要的参数
        Object[] args = pjp.getArgs();
        log.info("目标方法参数:{}", Arrays.toString(args));

        //执行原始方法
        Object returnValue = pjp.proceed();

        return returnValue;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

ThreadLocal(5):ThreadLocalMap源码分析

在分析ThreadLocal方法的时候,我们了解到ThreadLocal的操作实际上是围绕ThreadLocalMap展开的。ThreadLocalMap的源码相对比较复杂, 我们从以下三个方面进行讨论。 1 基本结构 ​ ThreadLocalMap是ThreadLocal的内部类,没有实现Map接口,用独…

Python三级考试笔记

Python三级考试笔记【源源老师】 三级标准 一、 理解编码、数制的基本概念,并且会应用。 1. 能够进行二进制、十进制以及十六进制之间的转换; 2. 理解Python中的数制转换函数。 二、 掌握一维数据的表示和读写方法,能够编写程序处理一维数据…

QT 文本编辑框textBrowser接收数据保持光标在底部的方法

目录 1.实现效果2.代码 1.实现效果 2.代码 右键textBrowser加入触发信号textChanged: 双击,跳转到槽函数:(文本更改时执行该函数) void Widget::updata_textBrowser() void Widget::on_textBrowser_textChanged() {//光标移动至底部ui->…

npm install 安装依赖如何加速

在使用npm安装依赖时,有几种方法可以加速这一过程,尤其是在面临网络限制或npm官方源速度慢的情况下。以下是一些常用的加速技巧: 1. 使用国内镜像源 国内有几个镜像源可以提供更快的下载速度,例如淘宝npm镜像。你可以通过以下命…

Android挖取原图中心区域RectF(并框线标记)放大到ImageView宽高,Kotlin

Android挖取原图中心区域RectF(并框线标记)放大到ImageView宽高,Kotlin 红色线框区域即为选中的原图中心区域,放大后放到等宽高的ImageView里面。 import android.content.Context import android.graphics.Bitmap import android.graphics.BitmapFactor…

Java - @JSONField和@JsonProperty注解

JSONField注解是阿里巴巴的fastjson框架中的注解,用于指定JSON字符串中的属性名和Java对象中的属性名之间的映射关系 JsonProperty注解是Jackson框架中的注解,用法类似于JSONField,也是指定JSON字符串中的属性名和Java对象中的属性名之间的映…

浮毛克星:猫毛过敏也能解决?宠物空气净化器品牌推荐

有些人认为对猫咪过敏是因为它们在空气中漂浮的毛发导致的。实际上,大部分人对猫咪过敏是由于它们身上的一种微小蛋白质引起的。这种过敏原蛋白质附着在猫咪的一些皮屑上。猫咪通常会通过舔毛的方式保持自己的清洁,舔毛的过程会导致这些皮屑携带蛋白质进…

阿赵UE学习笔记——16、渲染性能相关

阿赵UE学习笔记目录 大家好,我是阿赵。   继续学习虚幻引擎的使用。上一篇说了灯光的移动性问题,这一篇来看看怎样去辨别灯光性能的好坏。   虚幻引擎里面有一组显示模式是专门用来看场景当前的灯光和网格渲染的,叫做优化试图模式&#x…

基于java eclipse+jsp+mysql+servlet+Spring的学生信息管理系统基础版

基于java eclipsejspmysqlservletSpring的学生信息管理系统基础版 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写> 博主介绍:5年java开发经验,专注Java开发、定制、远程、文档编写指导等,csdn特邀作者、专注于…

营销系统黑名单优化:位图的应用解析 | 京东云技术团队

背景 营销系统中,客户投诉是业务发展的一大阻碍,一般会过滤掉黑名单高风险账号,并配合频控策略,来减少客诉,进而增加营销效率,减少营销成本,提升营销质量。 营销系统一般是通过大数据分析建模…

Linux 驱动开发基础知识——APP 怎么读取按键值(十二)

个人名片: 🦁作者简介:学生 🐯个人主页:妄北y 🐧个人QQ:2061314755 🐻个人邮箱:2061314755qq.com 🦉个人WeChat:Vir2021GKBS 🐼本文由…

使用Dasviewer时怎样让分屏且全屏状态下,模型能持续转动?

答:轻微甩动下就可以旋转了,此外直接拖入打开模型也可以旋转。 DasViewer是由大势智慧自主研发的免费的实景三维模型浏览器,采用多细节层次模型逐步自适应加载技术,让用户在极低的电脑配置下,也能流畅的加载较大规模实景三维模型,提供方便快捷的数据浏览…

STL常用之vector,list,stack,queue,deque总结与对比

一,vector 1)底层 vector的底层是开辟出来的一块连续空间,类似于数组,每次空间满了之后会根据不同的编译器有不同的扩容倍数。 2)优劣 优点:随机访问效率高,因为地址是连续的,底层…

接口测试总结及其用例设计方法

接口测试的总结文档 第一部分:主要从问题出发,引入接口测试的相关内容并与前端测试进行简单对比,总结两者之前的区别与联系。但该部分只交代了怎么做和如何做?并没有解释为什么要做? 第二部分:主要介绍为什…

【JavaEE】_synchronized关键字——监视器锁monitor lock

目录 1. synchronized的特性 2. synchronized的使用 3. Java标准库中的线程安全类 1. synchronized的特性 (1)互斥: 前文已经介绍,某个线程执行到某个对象的synchronized中时,其他线程如果也执行到同一个对象&…

卡在Installing CocoaPods dependencies (this may take a few minutes)这一步

卡在了Installing CocoaPods dependencies (this may take a few minutes)这一步怎么办 前置条件 注意一:请不要在目录、文件名中使用中文、空格等特殊符号。请不要单独使用常见的关键字作为项目名(如 class, native, new, package 等等)。请…

IP详细地理位置查询:技术原理与应用实践

IP地址是互联网上设备的唯一标识,在网络安全、个性化服务等领域具有重要意义。通过IP详细地理位置查询,可以获取到IP地址所在地的具体信息,为网络管理、定位服务等提供支持。IP数据云将深入探讨IP详细地理位置查询的技术原理、应用实践以及相…

Android Studio插件版本与Gradle 版本对应关系

关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。 专注于分享各领域原创系列文章 ,擅长java后端、移动开发、商业变现、人工智能等,希望大家多多支持。 目录 一、导读二、概览三、Gradle各版本对应关系3.1 Gradle 版…

psm的stata实现

1. PSM 简介 在经济学中,我们通常希望评估某项公共政策实施后的效应,为此,我们构建 "处理组" 和 "控制组" 以评估「处理效应 (treatment effect)」。然而,我们的数据通常来自非随机的观察研究中,处…

如何使用IP代理解决亚马逊账号IP关联问题?

亚马逊账号IP关联问题是指当同一个IP地址下有多个亚马逊账号进行活动时,亚马逊会将它们关联在一起,从而可能导致账号被封禁或限制。 为了避免这种情况,许多人选择使用IP代理。 IP代理为什么可以解决亚马逊IP关联问题? IP代理是…