Spring学习笔记12 面向切面编程AOP

news2024/11/24 5:09:08

Spring学习笔记11 GoF代理模式_biubiubiu0706的博客-CSDN博客

AOP(Aspect Oriented Programming):面向切面编程,面向方面编程.

AOP是对OOP的补充延申.底层使用动态代理实现.

Spring的AOP使用的动态代理是:JDK动态代理_CGLIB动态代理技术.Spring在这两种动态代理中灵活切换.如果是代理接口,会默认使用JDK动态代理,如果要代理某个类,这个类没有实现接口,就会切换使用CGLIB.当然,也可以强制通过一些配置让Spring只使用CGLIB

日志,事务管理,安全这些交叉业务(非业务代码)都属于AOP

AOP是一种思想,JDK代理,CGLIB代理都是AOP思想的实现,Spring AOP底层用的就是JDK代理和CGLIB代理

面向切面编程的七大术语:

连接点  Joinpoint:在程序的整个执行流程中,可以织入切面的位置.方法的执行前,异常抛出之后等位置.----->我的理解连接点就是用户需要被扩展的方法,其实我们将自定义注解放到目标方法上做标识,那么该注解其实就是一个连接点 连接点描述的是位置

切入点 Pointcut:程序执行流程中,真正织入切面的方法.(一个切入点对应多个连接点).---->我的理解:用户实际扩展的方法,确定了连接点,那么该方法就是一个切入点

通知 Advice: 扩展方法的具体实现

        1.前置通知:在目标方法执行之前执行  @Before

        2.后置通知:在目标方法执行之后,返回时执行  @AfterReturning

        3.环绕通知:在目标方法执行前后,都要执行的通知 也可以控制方法是否执行  @Around

        4.异常通知:在目标方法执行之后,抛出异常时执行  @AfterThrowing

        5.最终通知:无论程序是否执行成功,都要最后执行的通知   @After

切面 Aspect:切入点+通知就是切面

织入 Weaving:把通知应用到目标对象的过程

代理对象 Proxy:一个目标对象被织入通知后产生的新对象

目标对象 Target:被织入通知的对象

切入点表达式:

概念:当程序满足切入点表达式,才能进入切面,执行通知方法

1.bean("Bean的id") 根据Bean名称 进行拦截 只能匹配一个

2.within("com.example") 包名+类名 可以使用通配符*? 能匹配多个

3.execution(返回值类型  包名.类名.方法名(参数列表))

4.annotation(包名.注解名)

使用SpringAOP

Spring对AOP的实现包括已下3种方式:

第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式

第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式

第三种方式:Spring框架自己实现的AOP,基于XML配置方式

实际开发中,都是Spring+AspectJ来实现AOP,重点第一种和第二种方式.

准备环境

新建模块spring-aspectj-xml

导入依赖

<dependencies>
    <!--Spring依赖种包含Spring-aop依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.10</version>
    </dependency>
    <!--Spring aspectj依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.0.10</version>
    </dependency>
    <!--junit依赖-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

Spring配置文件中添加context命名空间和aop命名空间

先搭个架子

配置文件    注意下  这个注释可能没写对

默认情况下,<aop:aspectj-autoproxy />会使用Spring的JDK动态代理来代理目标对象。这意味着如果你的目标对象实现了至少一个接口,Spring将会使用JDK动态代理。这种代理方式仅代理实现了接口的方法。

如果目标对象没有实现任何接口,Spring将会使用CGLIB(Code Generation Library)来创建一个子类代理。CGLIB允许代理类继承目标对象,因此可以代理非接口类型的目标对象。

<aop:aspectj-autoproxy proxy-target-class="true" />
是强制使用CGLIB代理意思

测试,当调用业务层login方法时候

业务层新增一个方法

如果我在切入点表达式中修改下

可见切入点表达式很重要

新加一个业务类

修改切入点表达式

测试

下面引入所有通知

将UserService注释掉  避免看着混乱

package com.example.aspect;

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

/**
 * @author hrui
 * @date 2023/9/26 0:33
 */
@Component
@Aspect//标注是一个切面
public class LogAspect {//切面

    //切面=切入点+通知
    //通知就是增强,就是具体要编写的增强代码

    //前置通知   方法执行之前
    @Before("execution(* com.example.service..*(..))")//里面要写切入点表达式 UserService里的所有方法
    public void 增强(){
        System.out.println("我是一个通知,我是一段增强代码");
    }

    //后置通知   方法执行之后返回时执行
    @AfterReturning("execution(* com.example.service..*(..))")
    public void afterAdvice(){
        System.out.println("方法执行之后通知");
    }

    //环绕通知 目标方法执行之前和执行之后   并且在前置通知之前  在后置通知之后
    @Around("execution(* com.example.service..*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位
        Object result = null;
        try {
            System.out.println("执行方法前");
            //调用目标方法
            result=joinPoint.proceed();
            System.out.println("执行方法后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }finally {

        }
        return result;
    }

    //异常通知 发生异常之后
    @AfterThrowing(value = "execution(* com.example.service..*(..))",throwing = "e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录
    public void afterThrowingAdvice(){
        System.out.println("报错了我就执行了");
    }

    //最终通知asd
    @After("execution(* com.example.service..*(..))")
    public void after(){
        System.out.println("最后我肯定会执行");
    }

}

当系统有多个切面时候

加个切面

可以用@Order排序   数字小在前面

测试

通用切入点

切面上的代码,每个切入点都要写切入点表达式,代码冗余

package com.example.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

/**
 * @author hrui
 * @date 2023/9/26 0:33
 */
@Component
@Aspect//标注是一个切面
@Order(2)
public class LogAspect {//切面

    //切面=切入点+通知
    //通知就是增强,就是具体要编写的增强代码


    @Pointcut("execution(* com.example.service..*(..))")
    public void 通用切点(){
        //这个方法只是一个标记,方法名随意,方法体也不需要写任何代码
    }


    //前置通知   方法执行之前
    @Before("通用切点()")//里面要写切入点表达式 UserService里的所有方法
    public void 增强(){
        System.out.println("我是一个通知,我是一段增强代码");
    }

    //后置通知   方法执行之后返回时执行
    @AfterReturning("通用切点()")
    public void afterAdvice(){
        System.out.println("方法执行之后通知");
    }

    //环绕通知 目标方法执行之前和执行之后   并且在前置通知之前  在后置通知之后
    @Around("通用切点()")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位
        Object result = null;
        try {
            System.out.println("执行方法前");
            //调用目标方法
            result=joinPoint.proceed();
            System.out.println("执行方法后");
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }finally {

        }
        return result;
    }

    //异常通知 发生异常之后
    @AfterThrowing(value = "通用切点()",throwing = "e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录
    public void afterThrowingAdvice(){
        System.out.println("报错了我就执行了");
    }

    //最终通知asd
    @After("通用切点()")
    public void after(){
        System.out.println("最后我肯定会执行");
    }

}

测试

范式

package com.jt.aop;


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

import java.util.Arrays;

/**
 *  AOP(面向切面编程) 主要利用**动态代理**的模式 **降低程序的耦合度,扩展业务功能方法.**
 * 1.AOP需要被Spring容器管理 @Component
 * 2.标识该类是AOP切面   @Aspect
 * 关于AOP名词介绍
 * 1).连接点: 用户可以被扩展的方法    其实我们将自定义注解放到目标方法上做标识,那么该注解其实就是个连接点
 * 2).切入点: 用户实际扩展的方法      确定了连接点,那么该方法也就是个切入点
 * 3).通知:  扩展方法的具体实现       5个通知
 * 4).切面: 将通知应用到切入点的过程
 *
 *   通知类型(必会)
 * 1. before:  在目标方法执行之前执行
 * 2. afterReturning: 在目标方法执行之后返回时执行
 * 3. afterThrowing:  在目标方法执行之后,抛出异常时执行
 * 4. after:	无论程序是否执行成功,都要最后执行的通知
 * 5. around: 在目标方法执行前后 都要执行的通知(完美体现了动态代理模式)
 * 	功能最为强大 只有环绕通知可以控制目标方法的执行
 *
 * 关于通知方法总结:
 * 	1.环绕通知是处理业务的首选.  可以修改程序的执行轨迹
 * 	2.另外的四大通知一般用来做程序的监控.(监控系统) 只做记录
 * @author TB
 * @date 2020/2/12 0:24
 */
@Component
//虽然标识了该类为AOP切面 但是Spring容器默认不能识别切面注解,需要手动配置
//需要在配置类SpringConfig里加上注解@EnableAspectJAutoProxy
@Aspect
public class SpringAOP {
    /**
     * 切入点表达式
     * 概念:当程序满足切入点表达式,才能进入切面,执行通知方法.
     *
     * 1.bean("bean的ID")  根据beanId进行拦截  只能匹配一个
     * 2.within("包名.类名") 可以使用通配符*?      能匹配多个.
     * 	粒度: 上述的切入点表达式 粒度是类级别的.  粗粒度.
     * 3.execution(返回值类型   包名.类名.方法名(参数列表...))
     * 	粒度: 控制的是方法参数级别. 所以粒度较细.   最常用的.
     * 4.@annotation(包名.注解名)     只拦截注解.
     * 	粒度: 注解是一种标记 根据规则标识某个方法/属性/类    细粒度
     */

    /**
     * 切入点表达式练习
     * within:
     *  1.within(com.jt.*.DeptServiceImpl)   一级包下的类
     *  2.within(com.jt..*.DeptServiceImpl)  ..代表多级包下的类
     *  3.within(com.jt..*)  包下的所有的类
     *
     * execution(返回值类型 包名.类名.方法名(参数列表))
     *  1.execution(* com.jt..*.DeptServiceImpl.add*())
     *  注释: 返回值类型任意的, com.jt下的所有包中的DeptServiceImpl的类
     *        的add开头的方法 ,并且没有参数.
     *
     *  2.execution(* com.jt..*.*(..))
     *  注释: 返回值类型任意,com.jt包下的所有包的所有类的所有方法 任意参数.
     *
     *  3.execution(int com.jt..*.*(int))
     *  4.execution(Integer com.jt..*.*(Integer))
     *  强调: 在Spring表达式中没有自动拆装箱功能! 注意参数类型
     *
     * @annotation(包名.注解名)
     *     @Before("@annotation(com.jt.anno.Cache)")
     *    只拦截特定注解的内容.
     */


    //1.定义before通知
    //@Before("bean(deptServiceImpl)")//扫描的是一个类 因此该类里所有方法都被扩展到了
    //@Before("within(com.jt.service.DeptServiceImpl)")//和上面效果一样
    //@Before("execution(* com.jt.service.DeptServiceImpl.add*())")//*表示返回值类型任意 add*表示以add开头的方法名 最后()表示参数是空的
    //@Before("@annotation(com.jt.anno.Cache)")//意思有该注解 就作为切入点   因此用注解标识最常用(自定义个注解)

    /**
     * spring为了AOP动态获取目标对象及方法中的数据,则通过Joinpoint
     * JoinPoint是所有通知的公共参数,无论哪种通知里都可以使用
     * 在Before里可以获取
     * 对象做数据传递获取如:
     * 1.获取目标对象的类型
     * 2.获取目标方法的名称
     * 3.获取目标方法的参数
     * @param joinPoint
     */
    @Before("pointcut()")
    public void before(JoinPoint joinPoint){//前置方法一般作用获取参数,方法名,等等
        System.out.println("目标对象的Class类对象: "+joinPoint.getTarget().getClass());
        System.out.println("获取目标方法的方法签名: "+joinPoint.getSignature());
        System.out.println("获取目标对象的类名: "+ joinPoint.getSignature().getDeclaringTypeName());
        System.out.println("获取目标对象方法名: "+ joinPoint.getSignature().getName());
        System.out.println("获取目标方法参数: "+ Arrays.toString(joinPoint.getArgs()));
        System.out.println("我是before通知");
    }


    //1.定义一个切入点
    @Pointcut("@annotation(com.jt.anno.Cache)")
    public void pointcut(){

    }

    //如果每个通知前都加个切入点表达式  那么也太冗余了 因此我们可以定义个切入点 其他通知都围绕切入点
    //@BafterReturning("@annotation(com.jt.anno.Cache)")
    /**
     * JoinPoint参数是所有通知方法公有的
     *AfterReturning是目标方法返回执行之后返回时执行
     * 可以记录方法的返回值
     * AfterReturning注解里 value和pointcut是相同的效果:也就是说
     * @AfterReturning(value="pointcut()",returning="result")和@AfterReturning(pointcut="pointcut()",returning="result")
     * 效果一样
     * returning:将方法的返回值,通过形参result(这个随便取名)来进行传递(Spring会将返回值赋值给你定义的这个变量)
     */
    @AfterReturning(value="pointcut()",returning="result")
    public void afterReturning(JoinPoint joinPoint,Object result){//这里注意 如果有需要用到JointPoint参数 那么必须放在第一个位置  不用可以去掉
        System.out.println("目标返回值结果是: "+result);
        System.out.println("我是AfterReturning的通知");
    }

    @AfterThrowing(pointcut = "pointcut()",throwing="e")//当目标方法执行时,抛出异常时,可以用AfterThrowing记录
    public void afterThrowing(Exception e){
        System.out.println("获取目标异常信息: "+e.getMessage());
        System.out.println("获取目标异常类型: "+e.getClass());
        System.out.println("我是AfterThrowing的通知,出现异常了");
    }

    @After("pointcut()")
    public void after(){
        System.out.println("我是After的通知");
    }


    /**
     * 关于环绕通知的说明
     * 作用: 可以控制目标方法是否执行.
     * 参数: ProceedingJoinPoint 通过proceed方法控制目标方法执行.
     * 注意事项:
     *  ProceedingJoinPoint 只能适用环绕通知
     * @return
     */
    @Around("pointcut()")
    public Object around(ProceedingJoinPoint joinPoint){//注意多参情况ProceedingJoinPoint要放第一位
        Object result = null;
        try {
            System.out.println("环绕通知开始");
            //1.执行下一个通知  2.执行目标方法 3.接收返回值
            Long start = System.currentTimeMillis();
            result = joinPoint.proceed();
            Long end = System.currentTimeMillis();
            System.out.println("耗时:"+(end-start));
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        }
        System.out.println("环绕通知结束");
        return result;
    }
}

关于全注解式开发,不用Spring配置文件

测试也需要改下

Spring AOP基于XML方式的实现

新建模块spring-aspectj-xml2

依赖

<dependencies>
    <!--Spring依赖种包含Spring-aop依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.0.10</version>
    </dependency>
    <!--Spring aspectj依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.0.10</version>
    </dependency>
    <!--junit依赖-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13.2</version>
        <scope>test</scope>
    </dependency>
</dependencies>

 目标类

切面类

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

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

相关文章

Lua多脚本执行

--全局变量 a 1 b "123"for i 1,2 doc "Holens" endprint(c) print("*************************************1")--本地变量&#xff08;局部变量&#xff09; for i 1,2 dolocal d "Holens2"print(d) end print(d)function F1( ..…

没有 RunInstallerAttribute.Yes 的公共安装程序

安装window服务时报“没有 RunInstallerAttribute.Yes 的公共安装程序”的问题

Pytorch单机多卡分布式训练

Pytorch单机多卡分布式训练 数据并行&#xff1a; DP和DDP 这两个都是pytorch下实现多GPU训练的库&#xff0c;DP是pytorch以前实现的库&#xff0c;现在官方更推荐使用DDP&#xff0c;即使是单机训练也比DP快。 DataParallel&#xff08;DP&#xff09; 只支持单进程多线程…

Aqara秋季品鉴会众新品亮相 空间智能全面进化

9月26日&#xff0c;全球IoT独角兽Aqara以“空间智能&#xff0c;全面进化”为主题在北京三里屯CHAO酒店举办了秋季品鉴会。会上正式发布了方舟智慧中枢M3、卡农智能墙壁开关Z1 Pro、银河系列高端灯具V1以及具备全新功能升级的场景传感器FP2&#xff0c;方舟技术升级至2.0版本。…

浅谈Deep Learning 与 Machine Learning 与Artificial Intelligence

文章目录 三者的联系与区别 三者的联系与区别 “Deep Learning is a kind of Machine Learning, and Machine Learning is a kind of Artificial Intelligence.” 人工智能&#xff08;AI&#xff09;&#xff0c;机器学习&#xff08;Machine Learning&#xff0c;简称ML&am…

玩转 CODING 自动化助手,助力高效研发!

点击链接了解详情 在日常工作中&#xff0c;您是否会遇到下面的情况&#xff1a; 作为研发人员&#xff0c;从需求拆分出来的开发子任务完成时&#xff0c;还要手动修改需求为完成状态&#xff0c;不仅耗时还容易遗漏&#xff1b; 作为产品经理&#xff0c;每天都要关注需求/任…

游戏开发过程中需要注意哪些问题呢?

游戏开发是一个复杂的过程&#xff0c;需要注意多个方面的问题。以下是一些需要特别关注的关键问题&#xff1a; 游戏设计&#xff1a; 确定游戏的核心玩法和目标受众。 制定详细的游戏设计文档&#xff0c;包括角色、关卡设计、游戏机制和故事情节。 技术选择&#xff1a;…

项目04-基于Docker的Prometheus+Grafana+AlertManager的飞书监控报警平台

文章目录 一.项目介绍1.流程图2.拓扑图3.详细介绍 二.前期准备1.项目环境2.IP划分 三. 项目步骤1.ansible部署软件环境1.1 安装ansible环境1.2 建立免密通道1.3 批量部署docker 2 部署nginx、MySQL以及cadvisor、exporter节点2.1 在nginx节点服务器上面配置nginx、node_exporte…

高效批量剪辑的秘诀与技巧,虚化背景技巧在视频剪辑中的应用与创意

你是否曾经为了制作一个高质量的视频而感到烦恼&#xff1f;视频剪辑是一项繁琐的工作&#xff0c;但是使用批量剪辑工具可以让这个过程变得更加高效。今天&#xff0c;我们将向您介绍一款强大的批量剪辑工具——视频工厂&#xff0c;帮助您轻松制作高质量视频。 首先&#xff…

linux中mysql启动失败以及数据迁移

背影&#xff1a;服务启动失败&#xff1a;报错数据库连接太多导致mysql挂了 解决过程&#xff1a; 在任何部署信息都不知道的前提下&#xff08;因为是被临时拉来解决的&#xff09;&#xff1a; 1、通过【find / -name mysql】或者【whereis mysql】查找&#xff08;ps&am…

两表查询常用SQL

1、两个表&#xff1a;table_a和table_b&#xff0c;求两表的交集&#xff0c;关键字&#xff1a;INNER JOIN SELECT a.*,b.* FROM table_a AS a INNER JOIN table_b AS b ON a.idb.id&#xff1b; 2、两个表&#xff1a;table_a和table_b&#xff0c;table_a为主表&#xff0…

新旅程、新经营丨神策 2023 数据驱动大会 10 月 27-28 日北京见

以数据驱动为手段 以客户旅程为抓手 实现更好的数字化客户经营 「新旅程、新经营&#xff0c;决胜数字化」 神策 2023 数据驱动大会 报名通道正式开启 识别二维码立即报名 历经八载&#xff0c;神策数据驱动大会已成为国内数字化转型和营销科技领域的年度盛会&#xff01;本届大…

这个国庆场景下的创意数据应用,体现了数字经济时代的商业价值

在生成式AI爆火的2023年&#xff0c;数据协作和数据交换的商业价值越来越明显。大模型的训练正需要海量跨领域数据的“投喂”&#xff0c;才能真正创造商业价值涌现的奇迹。而如何在保护数据安全的前提下&#xff0c;有效发挥数据资产的商业价值&#xff0c;成为企业数字化亟需…

[异构图-论文阅读]Heterogeneous Graph Transformer

这篇论文介绍了一种用于建模Web规模异构图的异构图变换器(HGT)架构。以下是主要的要点: 摘要和引言 (第1页) 异构图被用来抽象和建模复杂系统,其中不同类型的对象以各种方式相互作用。许多现有的图神经网络(GNNs)主要针对同构图设计,无法有效表示异构结构。HGT通过设计…

【力扣2656】K个元素的最大和

&#x1f451;专栏内容&#xff1a;力扣刷题⛪个人主页&#xff1a;子夜的星的主页&#x1f495;座右铭&#xff1a;前路未远&#xff0c;步履不停 目录 一、题目描述二、题目分析 一、题目描述 题目链接&#xff1a;K个元素的最大和 给你一个下标从 0 开始的整数数组 nums 和…

【kkFileView】源码编译打包构建镜像部署

目录 官网使用源码构建镜像k8s启动yaml参考使用介绍 官网 官网: http://kkfileview.keking.cn/zh-cn/index.html在线文档: http://kkfileview.keking.cn/zh-cn/docs/home.html源码地址: https://gitee.com/kekingcn/file-online-preview发行版下载页面: https://gitee.com/kek…

数据分析技能点-正态分布和其他变量分布

在数据驱动的世界里,了解和解释数据分布是至关重要的。不同类型的数据分布,如正态分布、二项分布和泊松分布,具有不同的特性和应用场景。这些分布不仅在统计学和数据科学中有广泛应用,而且在日常生活和商业决策中也起着关键作用。 文章目录 正态分布正态分布和偏差其他常见…

使用adb命令通过数据线操控Android手机设备屏幕

目录 第一步&#xff1a;下载并安装Android SDK Platform-Tools 第二步&#xff1a;启动adb并测试连接 第三步&#xff1a;操控手机 第一步&#xff1a;下载并安装Android SDK Platform-Tools 进入Android开发者网站上找到ADB工具包&#xff08;包含在Android SDK Platform…

最新AI智能写作系统ChatGPT源码/支持GPT4.0+GPT联网提问/支持ai绘画Midjourney+Prompt+MJ以图生图+思维导图生成

一、AI创作系统 SparkAi系统是基于很火的GPT提问进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。那么如何搭建部署AI创作ChatGPT系统&#xff1f;小编这里写一个详细图文教程吧&#x…

没有一技之长,该如何找工作?

很负责任的告诉你&#xff0c;跟你一样有这个困惑的人真的太多了&#xff01; 而且你也会发现&#xff0c;你身边的大多数人也都很迷茫。 家庭、学历一般&#xff0c;没啥特长爱好&#xff0c;更没有拿的出手的技能。 想要告诉你的是&#xff0c;你觉得你自己一无所长&#…