SpringAOP笔记【JavaEE】

news2025/4/11 13:22:20

SpringAOP

一、AOP理解

AOP是一种思想,SpringAOP是一个框架,提供了一种对AOP思想的实现,他们的关系就像MVC和SpringMVC、IOC与DI类似。

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方
式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个
热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑
的各个部分进行隔离【对某一类事情的集中处理】,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

二、AOP功能

举个例子,我们在做后台管理系统时,除了登录和注册页面不需要做用户登录验证,其他所有页调用前端控制器都需要验证用户的登陆状态,如果每个前端控制器都写一遍 用户登陆验证,当我们的功能越来越多,所需要的验证也就越来越多,而这些方法又是相同的,就会冗余浪费人力。所以我们对于这种功能统一并且使用地方较多的功能,可以考虑使用AOP来统一处理。

AOP能够实现的功能有:

  • 统一日志记录
  • 统一方法执行时间统计
  • 统一返回格式设置
  • 统一异常处理
  • 事务的开启和提交

使用AOP可以扩充多个对象的某个能力,所以说AOP是OOP的补充和完善。

三、AOP组成

1.切面(Aspect)

定义 AOP 是针对哪个统一功能的,这个功能就叫做一个切面,比如用户登录功能或方法的统计日志,他们就各自是一个切面。切面是由切点通知组成的。

2.连接点(Join Point)

所有可能触发AOP(拦截方法的点)称之为连接点。

3.切点(Pointcut)

定义AOP拦截的规则。

Pointcut 的作⽤就是提供⼀组规则(使⽤ AspectJ pointcut expression language 来描述)来
匹配 Join Point,给满⾜规则的 Join Point 添加 Advice。

切点相当于保存了众多连接点的一个集合(如果把切点看成一个表,而连接点就是表中一条一条的数据)。

4.通知(Advice)

规定AOP执行的时机和执行的方法。

切面的工作被称之为通知。

Spring 切⾯类中,可以在⽅法上使⽤以下注解,会设置⽅法为通知⽅法,在满⾜条件后会通知本
⽅法进⾏调⽤:

【1】前置通知 @Before

通知⽅法会在⽬标⽅法调⽤之前执⾏。

【2】后置通知 @After

通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。

【3】返回数据之后通知 @AfterReturning

通知⽅法会在⽬标⽅法返回后调⽤。

【4】抛出异常之后通知 @AfterThrowing

通知⽅法会在⽬标⽅法抛出异常后调⽤。

【5】环绕通知 @Around

通知包裹了被通知的⽅法,在被通知的⽅法通知之前和调⽤之后执
⾏⾃定义的⾏为。

在这里插入图片描述

四、SpringAOP实现

接下来我们使⽤ Spring AOP 来实现⼀下 AOP 的功能,完成的⽬标是拦截所有 UserController ⾥⾯的
⽅法,每次调⽤ UserController 中任意⼀个⽅法时,都执⾏相应的通知事件。
Spring AOP 的实现步骤如下:

(1)添加AOP框架支持

在pom.xml中添加如下配置:

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-bo
ot-starter-aop -->
<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

(2)定义切面

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

/**
 * @author SunYuHang
 * @date 2023-02-13 12:43
 * @ClassName : UserAspect  //类名
 */
@Aspect  //表面此类是一个切面
@Component
public class UserAspect {
    
}

(3)定义切点,设置拦截规则

//定义切点,使用AspectJ表达式语法
    @Pointcut("execution(* com.example.springaop.controller.UserController.* (..))")
    public void pointcut(){ }

在这里插入图片描述

*指的是拦截任意方法返回类型

com.example.springaop.controller 拦截包名

com.example.springaop.controller.UserController 拦截类名

com.example.springaop.controller.UserController.* 拦截类下所有方法

com.example.springaop.controller.UserController.* (…) 拦截类下所有方法的所有参数

(4)实现通知方法(在什么时机?执行什么方法?)

package com.example.springaop.aop;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import org.springframework.util.StopWatch;

/**
 * @author SunYuHang
 * @date 2023-02-13 12:43
 * @ClassName : UserAspect  //类名
 */
@Aspect  //表面此类是一个切面
@Component
public class UserAspect {
    
    //定义切点,使用AspectJ表达式语法
    @Pointcut("execution(* com.example.springaop.controller.UserController.* (..))")
    public void pointcut(){ }

    /**
     * 定义 pointcut 切点的前置通知
     * 在执行目标方法之前执行的方法就叫做前置通知
     */
    @Before("pointcut()")
    public void doBefore(){
        System.out.println("前置通知被执行!");
    }

    /**
     * 定义 pointcut 切点的后置通知
     * 通知⽅法会在⽬标⽅法返回或者抛出异常后调⽤。
     */
    @After("pointcut()")
    public void doAfter(){
        System.out.println("后置通知被执行!");
    }

    /**
     * 定义 pointcut 切点的返回数据之后通知
     * 通知⽅法会在⽬标⽅法返回后调⽤。
     */
    @AfterReturning("pointcut()")
    public void doAfterReturning(){
        System.out.println("返回数据之后通知被执行!");
    }


    //抛出异常之前通知
    @AfterThrowing("pointcut()")
    public void doAfterThrowing(){
        System.out.println("抛出异常之后通知被执行!");
    }

    //环绕通知
//    @Around("pointcut()")
//    public Object doAround(ProceedingJoinPoint joinPoint){
//        Object obj = null;
//        long start = System.currentTimeMillis();
//        long end = 0;
//        System.out.println("Around ⽅法开始执⾏");
//        try {
//            // 执⾏拦截⽅法
//            obj = joinPoint.proceed();
//        } catch (Throwable throwable) {
//            throwable.printStackTrace();
//        }finally {
//            end =System.currentTimeMillis();
//        }
//        System.out.println("Around ⽅法执行时间"+(end-start)+"ms");
//        System.out.println("Around ⽅法结束执⾏");
//        return obj;
//    }

    @Around("pointcut()")
    public Object doAround(ProceedingJoinPoint joinPoint){
        //spring 中的时间统计对象
        StopWatch stopWatch = new StopWatch();
        Object obj = null;
        System.out.println("Around ⽅法开始执⾏");
        try {
            stopWatch.start();//统计方法的执行时间,开始计时
            // 执⾏拦截⽅法
            obj = joinPoint.proceed();
            stopWatch.stop();//统计方法的执行时间,停止计时
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("Around ⽅法执行时间, "+joinPoint.getSignature().getName()+","+stopWatch.getTotalTimeMillis()+"ms");
        System.out.println("Around ⽅法结束执⾏");
        return obj;
    }

}

五、SpringAOP实现原理

Spring AOP 是构建在动态代理基础上,因此 Spring 对 AOP 的⽀持局限于⽅法级别的拦截

Spring AOP 动态代理实现:

  • JDK Proxy (JDK动态代理)
  • CGLIB Proxy : 默认情况下 Spring AOP 都会采用 CGLIB 来实现动态代理。【效率高】
    • CGLIB Proxy 实现原理:通过继承代理对象来实现动态代理(子类拥有父类的所有功能)

织入(Weaving):代理的生成时机

动态代理的实现

JDK动态代理实现

JDK 实现时,先通过实现 InvocationHandler 接⼝创建⽅法调⽤处理器,再通过 Proxy 来创建代
理类。

import org.example.demo.service.AliPayService;
        import org.example.demo.service.PayService;
        import java.lang.reflect.InvocationHandler;
        import java.lang.reflect.Method;
        import java.lang.reflect.Proxy;
//动态代理:使⽤JDK提供的api(InvocationHandler、Proxy实现),此种⽅式实现,要求被
        代理类必须实现接⼝
public class PayServiceJDKInvocationHandler implements InvocationHandler {

    //⽬标对象即就是被代理对象
    private Object target;

    public PayServiceJDKInvocationHandler( Object target) {
        this.target = target;
    }

    //proxy代理对象
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");
        //通过反射调⽤被代理类的⽅法
        Object retVal = method.invoke(target, args);
        //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }
    public static void main(String[] args) {
        PayService target= new AliPayService();
        //⽅法调⽤处理器
        InvocationHandler handler =
                new PayServiceJDKInvocationHandler(target);
        //创建⼀个代理类:通过被代理类、被代理实现的接⼝、⽅法调⽤处理器来创建
        PayService proxy = (PayService) Proxy.newProxyInstance(
                target.getClass().getClassLoader(),
                new Class[]{PayService.class},
                handler
        );
        proxy.pay();
    }
}

CGLIB动态代理实现

import org.springframework.cglib.proxy.Enhancer;
        import org.springframework.cglib.proxy.MethodInterceptor;
        import org.springframework.cglib.proxy.MethodProxy;
        import org.example.demo.service.AliPayService;
        import org.example.demo.service.PayService;
        import java.lang.reflect.Method;
public class PayServiceCGLIBInterceptor implements MethodInterceptor {
    //被代理对象
    private Object target;

    public PayServiceCGLIBInterceptor(Object target){
        this.target = target;
    }

    @Override
    public Object intercept(Object o, Method method, Object[] args, Method Proxy methodProxy) throws Throwable {
        //1.安全检查
        System.out.println("安全检查");
        //2.记录⽇志
        System.out.println("记录⽇志");
        //3.时间统计开始
        System.out.println("记录开始时间");
        //通过cglib的代理⽅法调⽤
        Object retVal = methodProxy.invoke(target, args);
        //4.时间统计结束
        System.out.println("记录结束时间");
        return retVal;
    }

    public static void main(String[] args) {
        PayService target= new AliPayService();
        PayService proxy= (PayService) Enhancer.create(target.getClass(),n ew PayServiceCGLIBInterceptor(target));
        proxy.pay();
    }
}

JDK 和 CGLIB 实现的区别

  1. JDK 实现,要求被代理类必须实现接⼝,之后是通过 InvocationHandler 及 Proxy,在运⾏
    时动态的在内存中⽣成了代理类对象,该代理对象是通过实现同样的接⼝实现(类似静态代
    理接⼝实现的⽅式),只是该代理类是在运⾏期时,动态的织⼊统⼀的业务逻辑字节码来完
    成。
  2. CGLIB 实现,被代理类可以不实现接⼝,是通过继承被代理类,在运⾏时动态的⽣成代理类
    对象。

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

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

相关文章

CHAPTER 1 Zabbix介绍及安装

Zabbix介绍及安装1.1 Zabbix监控1 为什么要监控1.1 网站可用性2 监控什么东西2.1 监控范畴3 怎么来监控3.1 远程管理服务器3.2 监控硬件3.3 查看cpu相关3.4 内存3.5 磁盘3.6 监控网络4 监控工具总览5 zabbix介绍5.1 zabbix的组成5.2 zabbix监控范畴1.2 安装zabbix1 环境检查2 安…

磁疗为什么“没效果”?原来真相是这样!

很多人磁疗之后&#xff0c; 总爱迫不及待问一个问题&#xff1a; “这个多长时间见效啊&#xff1f;” …… 还有些人几天没有效果&#xff0c; 就果断下结论&#xff1a; “这东西没用&#xff01;” …… 有不少人错误地把磁疗等同于“药品”一样看待&#xff0c;总觉得…

RPA+保险后台部门擦出不一样“火花” | RPA案例

在保险行业中&#xff0c;后台业务线主要是为前台和中台等提供支持&#xff0c;提供公司整体运营服务&#xff0c;包括财务、信息、人力、综合办等。相对于中前台部门&#xff0c;后台部门离核心价值链更远一些&#xff0c;更偏支持部门&#xff0c;其中某些岗位与业务相关度强…

金三银四面试必看,复盘字节测试开发面试:一次测试负责人岗位面试总结

最近面试了某企业的测试负责人岗位&#xff0c;历经四面&#xff0c;收获蛮多的。 这篇文章&#xff0c;我想聊聊这次面试过程中的一些经历&#xff0c;以及些许经验和教训。 岗位要求 岗位名称&#xff1a;测试负责人 岗位要求&#xff1a;1、扎实的技术以及丰富的技术项目…

【半监督医学图像分割 2021 CVPR】CVRL 论文翻译

文章目录【半监督医学图像分割 2021 CVPR】CVRL 论文翻译摘要1. 介绍1.1 总览1.2 无监督对比学习2. 实验3. 总结【半监督医学图像分割 2021 CVPR】CVRL 论文翻译 论文题目&#xff1a;Momentum Contrastive Voxel-wise Representation Learning for Semi-supervised Volumetric…

Linux中systemctl 服务管理

1、概述CentOS 7使用Systemd管理守护进程。centos7采用 systemd管理&#xff0c;服务独立的运行在内存中&#xff0c;服务响应速度快&#xff0c;但占用更多内存。独立服务的服务启动脚本都在目录 /usr/lib/systemd/system里。Systend的新特性&#xff1a;系统引导时实现服务的…

手写JavaScript常见5种设计模式

想分享的几种设计模式 目前模式&#xff1a;工厂模式&#xff0c;单例模式&#xff0c;适配器模式&#xff0c;装饰者模式&#xff0c;建造者模式 建造者模式 简介&#xff1a;建造者模式&#xff08;builder pattern&#xff09;比较简单&#xff0c;它属于创建型模式的一种…

QT入门Input Widgets之QScrollBar

目录 一、界面布局功能 1、界面位置介绍 2、控件界面基本属性 2.1 horizontalScrollBar界面属性 3、样式设置 此文为作者原创&#xff0c;创作不易&#xff0c;转载请标明出处&#xff01; 一、界面布局功能 1、界面位置介绍 QScrollBar主要分为两种&#xff0c;一种垂直…

C语言(字符串输入)

目录 一.gets和puts组合 二.fgets()和fputs() 三.fgets()函数返回 四.fgets读取满问题 五.修改fgets函数,自动用\0替换\n 一.gets和puts组合 Gets()读取整行输入&#xff0c;知道遇到换行符&#xff0c;然后丢弃换行符&#xff0c;存储其余字符&#xff0c;并在这些字符的…

学习笔记:文件

因为有的数据&#xff0c;数据量极大。或者是你想把编译输出的内容存储起来&#xff0c;就可以使用文件 读文件中内容具体操作 来自C语言详解 FILE文件操作 - 知乎 (zhihu.com) 写入文件具体操作 同样来自 C语言详解 FILE文件操作 - 知乎 (zhihu.com) 当文件关闭时&#xff0c…

sql手工注入dvwa靶场

sql手工注入dvwa靶场 记录一下自己重新开始学习web安全之路④。 一、找交互点&#xff08;url、搜索框、登录框&#xff09; 在dvwa靶场中&#xff0c;发现有url&#xff0c;有搜索框。 二、找注入点&#xff08;通过 ’ 号来判断&#xff09; 思考一&#xff1a;为什么能通…

响应式圣经:10W字,实现Spring响应式编程自由

前言 全链路异步化改造的基础是响应式编程 随着业务的发展&#xff0c;微服务应用的流量越来越大&#xff0c;使用到的资源也越来越多。 在微服务架构下&#xff0c;大量的应用都是 SpringCloud 分布式架构&#xff0c;这种架构总体上是全链路同步模式。 全链路同步模式不仅…

膳食锌缺乏或过量对人体肠道菌群及健康的影响

谷禾健康 锌与肠道微生物 锌(Zn)是人体必需的微量元素&#xff0c;是人体中第二丰富的矿物质。锌在细胞和器官功能中起着关键的催化、调节和结构作用。 ★ 膳食锌缺乏或过量均不健康 锌缺乏与发育不良、免疫功能低下、味觉丧失、不良妊娠结局、脱发、皮肤损伤和神经行为异常有关…

数据结构 | 树 | 二叉树

&#x1f525;Go for it!&#x1f525; &#x1f4dd;个人主页&#xff1a;按键难防 &#x1f4eb; 如果文章知识点有错误的地方&#xff0c;请指正&#xff01;和大家一起学习&#xff0c;一起进步&#x1f440; &#x1f4d6;系列专栏&#xff1a;数据结构与算法 &#x1f52…

windows本地开发Spark[不开虚拟机]

1. windows本地安装hadoop hadoop 官网下载 hadoop2.9.1版本 1.1 解压缩至C:\XX\XX\hadoop-2.9.1 1.2 下载动态链接库和工具库 1.3 将文件winutils.exe放在目录C:\XX\XX\hadoop-2.9.1\bin下 1.4 将文件hadoop.dll放在目录C:\XX\XX\hadoop-2.9.1\bin下 1.5 将文件hadoop.dl…

Redis学习【5】之集合的底层实现原理

文章目录一 集合的底层实现原理1.1 两种实现的选择1.2 zipList【存在于Redis7.0之前的版本】1.3 listPack【Redis7.0中zipList的改进版】1.4 skipList1.4.1 skipList 原理1.4.2 skipList存在的问题与优化1.5 quickList1.5.1 quitList检索操作1.5.2 quitList插入操作1.5.3 quitL…

知识图谱概述

知识图谱 知识图谱本质上是一种大规模的语义网络&#xff0c;富含实体、概念及其之间的各种语义关系。 作为一种语义网络是大数据时代知识表示的重要方式之一。 作为一种技术体系&#xff0c;是大数据时代知识工程代表性进展。 领域知识图谱 领 域&#xff08;行业&#xf…

一篇文章带你熟练使用Ansible中的playbook

目录 一、Playbook的功能 二、YAML 1、简介 2、特点 3、语法简介 4、YAML 列表 5、YAML的字典 三、playbook执行命令 四、 Playbook的核心组件 五、vim 设定技巧 练习 一、Playbook的功能 playbook 是由一个或多个play组成的列表 Playboot 文件使用YAML来写的 二、…

Mysql5.7安装【Windows版】

文章目录一、下载二、添加到环境变量三、添加配置文件my.ini四、安装Mysql 修改密码一、下载 下载地址 滑倒最下面有一个MySQL Community Server 选择要下载的版本 二、添加到环境变量 下载好了之后开始解压 把bin目录添加到环境变量 可以点击进入bin目录&#xff0c;直接复…

低代码平台真的是企业的福音吗?

研究低代码平台已有3年&#xff0c;也算是个低代码资深用户了&#xff0c;下面基于个人理解给大家做一份2k字的深入介绍&#xff01;希望对大家在低代码方面有一定帮助。 开篇&#xff0c;先带大家来看企业为什么要布局低代码平台&#xff01;究竟有何优势&#xff1f; &…