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

news/2024/7/24 9:33:40 标签: spring, 学习, 笔记

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.niftyadmin.cn/n/5054491.html

相关文章

js中的类型转换

原文地址 JavaScript 中有两种类型转换&#xff1a;隐式类型转换&#xff08;强制类型转换&#xff09;和显式类型转换。类型转换是将一个数据类型的值转换为另一个数据类型的值的过程。 隐式类型转换&#xff08;强制类型转换&#xff09;&#xff1a; 隐式类型转换是 Java…

【lesson12】理解进程地址空间

文章目录 什么是进程地址空间&#xff1f;进程地址空间的作用扩展内容初步理解深入理解 什么是进程地址空间&#xff1f; 故事&#xff1a; 背景&#xff1a;有一个大富豪&#xff0c;家里的存款有10亿美元&#xff0c;他有三个私生子三个人之间彼此互不相识&#xff0c;只有富…

【C语言】指针与动态内存

文章目录 1. 使用动态内存的意义2. 动态内存分配函数2.1 malloc2.2 free2.3 calloc2.4 realloc 3. 动态内存会被自动释放吗&#xff1f;4. 涉及动态内存常见的错误使用4.1 对NULL指针的解引用操作4.2 未对开辟的动态内存空间初始化4.3 越界访问4.4 对非动态开辟内存使用free释放…

Python 发送企业微信通知

通过requests模块对webhook地址发送post请求就能发送通知&#xff1a; import requests def send_weixin(content):url "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxxxxxx" # 这里就是群机器人的Webhook地址headers {"Content-Type": &…

WebGL实现透明物体(α混合)

目录 α混合 如何实现α混合 1. 开启混合功能&#xff1a; 2. 指定混合函数 混合函数 gl.blendFunc&#xff08;&#xff09;函数规范 可以指定给src_factor和dst_factor的常量 混合后颜色的计算公式 加法混合 半透明的三角形&#xff08;LookAtBlendedTriangl…

【无标题】verilog-hdl-farmat属于FPGA工程师的Verilog实用开发插件

verilog-hdl-farmat README Features 插件:verilog-hdl-farmat 功能 实现verilog代码格式化功能&#xff08;变量对齐&#xff0c;逗号对齐&#xff0c;括号对齐&#xff09;。功能触发&#xff1a;按下 ctrlshiftp :输入 verilog。 快捷键 CTRL L;一件例化功能,例化的代…

命令行程序测试自动化

【软件测试面试突击班】如何逼自己一周刷完软件测试八股文教程&#xff0c;刷完面试就稳了&#xff0c;你也可以当高薪软件测试工程师&#xff08;自动化测试&#xff09; 这几天有一个小工具需要做测试&#xff0c;是一个命令行工具&#xff0c;这个命令行工具有点类似mdbg等命…

【Linux】 df命令使用

df命令 df 命令&#xff0c;用于显示 Linux 系统中各文件系统的硬盘使用情况&#xff0c;包括文件系统所在硬盘分区的总容量、已使用的容量、剩余容量等 执行命令结果 各列信息的含义分别是&#xff1a; Filesystem&#xff1a;表示该文件系统位于哪个分区&#xff0c;因此该…