使用Spring的AOP

news2024/11/16 10:54:41

使用Spring的AOP

  • 一、AOP 的常用注解
    • 1.切面类@Aspect
    • 2.@Pointcut
    • 3.前置通知@Before
    • 4.后置通知@AfterReturning
    • 5.环绕通知@Around
    • 6.异常通知@AfterThrowing
    • 7.最终通知@After
    • 8.切面顺序@Order
    • 9.启用自动代理@EnableAspectJAutoProxy
  • 二、AOP注解方式开发
  • 三、AOP 全注解开发
  • 四、基于XML配置方式的AOP(了解)


  • Spring 对 AOP 的实现包括以下3种方式:
    • 第一种方式:Spring框架结合AspectJ框架实现的AOP,基于注解方式。
    • 第二种方式:Spring框架结合AspectJ框架实现的AOP,基于XML方式。
    • 第三种方式:Spring框架自己实现的AOP,基于XML方式。
  • 实际开发种都是Spring + AspectJ来实现的AOP。
  • 什么是AspectJ?(Eclipse组织的一个支持AOP的框架。AspectJ框架是独立于Spring框架之外的一个框架,Spring框架用了AspectJ)
  • AspectJ项目起源于帕洛阿尔托(Palo Alto)研究中心(缩写为PARC)。该中心由Xerox集团资助,Gregor Kiczales领导,从1997年开始致力于AspectJ的开发,1998年第一次发布给外部用户,2001年发布1.0 release。为了推动AspectJ技术和社团的发展,PARC在2003年3月正式将AspectJ项目移交给了Eclipse组织,因为AspectJ的发展和受关注程度大大超出了PARC的预期,他们已经无力继续维持它的发展。

一、AOP 的常用注解

1.切面类@Aspect

  • @Aspect作用是把当前类标识为一个切面供容器读取。

    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE})
    public @interface Aspect {
        String value() default "";
    }
    

2.@Pointcut

  • @Pointcut注解标注在方法上面,用来定义切入点。
  • 可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可。
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Pointcut {
        String value() default "";
        String argNames() default "";
    }
    

3.前置通知@Before

  • @Before目标方法执行之前的通知
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Before {
        String value();
        String argNames() default "";
    }
    

4.后置通知@AfterReturning

  • @AfterReturning目标方法执行之后的通知
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface AfterReturning {
        String value() default "";
        String pointcut() default "";
        String returning() default "";
        String argNames() default "";
    }
    

5.环绕通知@Around

  • @Around目标方法之前添加通知,同时目标方法执行之后添加通知。
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface Around {
        String value();
        String argNames() default "";
    }
    

6.异常通知@AfterThrowing

  • @AfterThrowing发生异常之后执行的通知
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface AfterThrowing {
        String value() default "";
        String pointcut() default "";
        String throwing() default "";
        String argNames() default "";
    }
    

7.最终通知@After

  • @After放在finally语句块中的通知
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface After {
        String value();
    
        String argNames() default "";
    }
    

8.切面顺序@Order

  • 我们知道,业务流程当中不一定只有一个切面,可能有的切面控制事务,有的记录日志,有的进行安全控制,如果多个切面的话,顺序如何控制:可以使用@Order注解来标识切面类,为@Order注解的value指定一个整数型的数字,数字越小,优先级越高。
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.TYPE, ElementType.METHOD, ElementType.FIELD})
    @Documented
    public @interface Order {
    	/**
    	 * The order value.
    	 * <p>Default is {@link Ordered#LOWEST_PRECEDENCE}.
    	 * @see Ordered#getOrder()
    	 */
    	int value() default Ordered.LOWEST_PRECEDENCE;
    }
    

9.启用自动代理@EnableAspectJAutoProxy

  • 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。
    @Target(ElementType.TYPE)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    @Import(AspectJAutoProxyRegistrar.class)
    public @interface EnableAspectJAutoProxy {
    	boolean proxyTargetClass() default false;
    	boolean exposeProxy() default false;
    }
    

二、AOP注解方式开发

  • 注意:本文使用了 log4j2 日志,如果不知道可以看我的博客 ===> Spring对IoC的实现中的第一个Spring程序

  • 注意本文也使用了 junit 进行单元测试。

  • 使用Spring+AspectJAOP需要引入的依赖如下:

    <!--spring的核心依赖 aop core beans jcl expression 等-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>6.1.4</version>
    </dependency>
    <!-- AOP 依赖的AspectJ -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>6.1.4</version>
    </dependency>
    
  • Spring配置文件中添加context命名空间和aop命名空间

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    
    </beans>
    
  • 第一步:定义目标类以及目标方法

    package com.gdb.service;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    //目标类
    @Service
    public class OrderService {
        private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
        //目标方法
        public void detail() {
            logger.info("正在打印订单详情......");
        }
    }
    
  • 第二步:编写切面类

    package com.gdb.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    //切面类(通知+切点 = 切面)
    @Aspect
    @Component
    public class MyAspect {
        private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);
    
    	//这是需要增强的代码(通知)
        @Before("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法
        public void beforeAdvice() {
            logger.info("前置通知执行了");
        }
    
        @AfterReturning("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法,
        public void afterReturningAdvice() {
            logger.info("后置通知执行了");
        }
    
        @Around("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法,
        public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            logger.info("前置环绕通知执行了");
            proceedingJoinPoint.proceed(); // 执行目标方法
            logger.info("后置环绕通知执行了");
        }
    
        @AfterThrowing("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法,
        public void afterThrowingAdvice() {
            logger.info("异常通知执行了");
        }
    
        @After("execution(* com.gdb.service..* (..))") // com.gdb.service包下的所有方法,
        public void afterAdvice() {
            logger.info("最终通知执行了");
        }
    }
    
  • 第三步:在配置文件中启动包扫描启用自动代理

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                               http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                               http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--开启组件扫描-->
        <context:component-scan base-package="com.gdb"/>
        <!--开启自动代理-->
        <aop:aspectj-autoproxy proxy-target-class="false"/>
        <!--
            <aop:aspectj-autoproxy  proxy-target-class="true"/> 开启自动代理之后,凡事带有@Aspect注解的bean都会生成代理对象。
            proxy-target-class="true" 表示采用cglib动态代理。
            proxy-target-class="false" 表示采用jdk动态代理。默认值是false。即使写成false,当没有接口的时候,也会自动选择cglib生成代理类。
        -->
    </beans>
    
  • 第四步:编写测试程序

    @Test
    public void test(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.detail();
    }
    
  • 第五步:执行结果
    在这里插入图片描述

    • 通过上面的执行结果就可以判断他们的执行顺序了,这里不再赘述。
  • 第六步:结果中没有异常通知,这是因为目标程序执行过程中没有发生异常。我们尝试让目标方法发生异常:

    package com.gdb.service;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Service;
    
    @Service
    public class OrderService {
        private static final Logger logger = LoggerFactory.getLogger(OrderService.class);
    
        public void detail() {
            logger.info("正在打印订单详情......");
            throw new RuntimeException();
        }
    }
    
  • 第七步:执行结果:
    在这里插入图片描述

    • 通过测试得知,当发生异常之后,最终通知也会执行,因为最终通知@After会出现在finally语句块中。出现异常之后,后置通知环绕通知的结束部分不会执行。
  • 优化使用切点表达式:

    • 上面编写的切面类的缺点是:
      • 第一:切点表达式重复写了多次,没有得到复用。
      • 第二:如果要修改切点表达式,需要修改多处,难维护。
  • 可以这样做:将切点表达式单独的定义出来,在需要的位置引入即可。

    package com.gdb.aspect;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class MyAspect {
        private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);
    
        @Pointcut("execution(* com.gdb.service..* (..))")
        public void pointcut() {
        }
    
        @Before("pointcut()") // com.gdb.service包下的所有方法
        public void beforeAdvice() {
            logger.info("前置通知执行了");
        }
    
        @AfterReturning("pointcut()") // com.gdb.service包下的所有方法,
        public void afterReturningAdvice() {
            logger.info("后置通知执行了");
        }
    
        @Around("pointcut()") // com.gdb.service包下的所有方法,
        public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
            logger.info("前置环绕通知执行了");
            proceedingJoinPoint.proceed();
            logger.info("后置环绕通知执行了");
        }
    
        @AfterThrowing("pointcut()") // com.gdb.service包下的所有方法,
        public void afterThrowingAdvice() {
            logger.info("异常通知执行了");
        }
    
        @After("pointcut()") // com.gdb.service包下的所有方法,
        public void afterAdvice() {
            logger.info("最终通知执行了");
        }
    }
    
  • 使用@Pointcut注解来定义独立的切点表达式。

  • 注意这个@Pointcut注解标注的方法随意,只是起到一个能够让@Pointcut注解编写的位置。


三、AOP 全注解开发

  • 第一步:就是编写一个类,在这个类上面使用大量注解来代替spring的配置文件,spring配置文件消失了,如下:
    package com.gdb.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration // 配置类
    @ComponentScan("com.gdb")
    @EnableAspectJAutoProxy(proxyTargetClass = false)
    public class SpringConfig {
    }
    
  • 第二步:测试程序也变化了
    @Test
    public void test() {
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
        OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
        orderService.detail();
    }
    
  • 第三步:执行结果
    在这里插入图片描述

四、基于XML配置方式的AOP(了解)

  • 第一步:编写目标类
package com.gdb.service;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class OrderService {
    private static final Logger logger = LoggerFactory.getLogger(OrderService.class);

    public void detail() {
        logger.info("正在打印订单详情......");
    }
}
  • 第二步:编写切面类,并且编写通知
package com.gdb.aspect;

import org.aspectj.lang.ProceedingJoinPoint;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class MyAspect {
    private static final Logger logger = LoggerFactory.getLogger(MyAspect.class);

    public void beforeAdvice() {
        logger.info("前置通知执行了");
    }

    public void afterReturningAdvice() {
        logger.info("后置通知执行了");
    }

    public void aroundAdvice(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        logger.info("前置环绕通知执行了");
        proceedingJoinPoint.proceed();
        logger.info("后置环绕通知执行了");
    }

    public void afterThrowingAdvice() {
        logger.info("异常通知执行了");
    }

    public void afterAdvice() {
        logger.info("最终通知执行了");
    }
}
  • 第三步:编写spring配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
	                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
	                           http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">

    <bean id="myAspect" class="com.gdb.aspect.MyAspect"/>
    <bean id="orderService" class="com.gdb.service.OrderService"/>

    <!--aop配置-->
    <aop:config>
        <!--切点表达式-->
        <aop:pointcut id="p" expression="execution(* com.gdb.service..* (..))"/>
        <!--切面-->
        <aop:aspect ref="myAspect">
            <!--切面=通知 + 切点-->
            <aop:before method="beforeAdvice" pointcut-ref="p"/>
            <aop:after-returning method="afterReturningAdvice" pointcut-ref="p"/>
            <aop:around method="aroundAdvice" pointcut-ref="p"/>
            <aop:after-throwing method="afterThrowingAdvice" pointcut-ref="p"/>
            <aop:after method="afterAdvice" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>
</beans>
  • 第四步:编写测试程序
@Test
public void test() {
    ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
    OrderService orderService = applicationContext.getBean("orderService", OrderService.class);
    orderService.detail();
}
  • 第四步:执行结果
    在这里插入图片描述
  • 通过结果可以看出来顺序和前面的不一样了,我感觉是配置文件中的顺序有关系,由于主要都是使用注解的方式。

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

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

相关文章

RDD算子介绍(二)

1. coalesce 用于缩减分区&#xff0c;减少分区个数&#xff0c;减少任务调度成本。 val rdd : RDD[Int] sc.makeRDD(List(1, 2, 3, 4), 4) val newRDD rdd.coalesce(2) newRDD.saveAsTextFile("output") 分区数可以减少&#xff0c;但是减少后的分区里的数据分布…

Keepalived+LVS构建高可用集群

目录 一、Keepalive基础介绍 1. Keepalive与VRRP 2. VRRP相关技术 3. 工作原理 4. 模块 5. 架构 6. 安装 7. Keepalived 相关文件 7.1 配置组成 7.2 全局配置 7.3 VRRP实例配置&#xff08;lvs调度器&#xff09; 7.4 虚拟服务器与真实服务器配置 二、Keepalived…

【数据库】软件测试之MySQL数据库面试总结

有表如下&#xff1a; Student 学生表 SC 成绩表 Course 课程表 Teacher 老师表 每个学生可以学习多门课程&#xff0c;每一个课程都有得分&#xff0c;每一门课程都有老师来教&#xff0c;一个老师可以教多个学生 1、查询姓‘朱’的学生名单 select * from Student whe…

010Editor汉化版+下载+注册码+模板bug

项目场景&#xff1a; 这天我想使用我的不知名的一个破解版本的010Edit来查看一个EXE程序&#xff0c;并想使用模板功能&#xff0c;但是发现没有该模板还无法下载最新模板 问题描述 010Edit联网后需要注册码&#xff1a; 010 Editor 激活码生成器 使用方法 参照教程使用0…

面试题:分布式锁用了 Redis 的什么数据结构

在使用 Redis 实现分布式锁时&#xff0c;通常使用 Redis 的字符串&#xff08;String&#xff09;。Redis 的字符串是最基本的数据类型&#xff0c;一个键对应一个值&#xff0c;它能够存储任何形式的字符串&#xff0c;包括二进制数据。字符串类型的值最多可以是 512MB。 Re…

浅谈Maven

Maven能为我们解决什么问题 1&#xff1a;添加第三方jar包 按照最原始的做法&#xff0c;我们是手动复制jar包到项目WEB-INF/lib下&#xff0c;每个项目都会有一份&#xff0c;造成大量重复文件。而Maven将jar包放在本地仓库中统一管理&#xff0c;需要jar包只需要用坐标的方式…

使用Canvas绘制一个自适应长度的折线图

要求x轴根据数据长度自适应 y轴根据数据最大值取长度值 <template><div ref"cvsContainer" class"cvs-container"><canvas ref"cvs" class"canvas"></canvas></div> </template><script set…

188基于matlab的AR模型参数估计

基于matlab的AR模型参数估计&#xff0c;burg法和ule-Walker法估计信号&#xff0c;并输出估计误差。程序已调通&#xff0c;可直接运行。 188 AR模型参数估计 burg法和ule-Walker法 (xiaohongshu.com)

离散数学例题——6.树和特殊图

树 树的证明 森林 同构树非同构树 生成树 有向树 二叉树遍历 哈夫曼树 特殊图 欧拉图&#xff08;一次边&#xff09; Fleury算法求欧拉回路 欧拉通路&#xff08;一笔画&#xff09; 哈密顿图&#xff08;一次点&#xff09; 哈密顿图的充分条件 哈密顿图必要条件 二部图 二部…

人工智能OCR领域安全应用措施

引言 编写目的 随着新一轮科技革命和产业变革的深入发展&#xff0c;5G、大数据、云计算、深度学习等新技术日益成为推动社会进步的核心动力。人工智能&#xff08;AI&#xff09;作为这些新技术的集大成者&#xff0c;正迅速成为新型基础设施建设的战略性支柱&#xff0c;其广…

【详识C语言】自定义类型之三:联合

本章重点 联合 联合类型的定义 联合的特点 联合大小的计算 联合&#xff08;共用体&#xff09; 联合类型的定义 联合也是一种特殊的自定义类型 这种类型定义的变量也包含一系列的成员&#xff0c;特征是这些成员公用同一块空间&#xff08;所以联合也叫共用体&#xff09;…

AI 辅助研发趋势

引言 随着人工智能技术的持续发展与突破&#xff0c;AI辅助研发正成为科技界和工业界瞩目的焦点。从医药研发到汽车设计&#xff0c;从软件开发到材料科学&#xff0c;AI正逐渐渗透到研发的各个环节&#xff0c;变革着传统的研发模式。在这一背景下&#xff0c;AI辅助研发不仅…

Linux学习——锁

目录 ​编辑 一&#xff0c;锁的概念 二&#xff0c;锁的操作 1&#xff0c;锁类型 pthread_mutex_t 2&#xff0c;初始化锁 3&#xff0c;上锁 4&#xff0c;解锁 5&#xff0c;销毁锁 三&#xff0c;线程安全问题演示 四&#xff0c;锁的原理 五&#xff0c;死锁 …

每日OJ题_牛客HJ73 计算日期到天数转换(IO型OJ)

目录 牛客HJ73 计算日期到天数转换 解析代码 牛客HJ73 计算日期到天数转换 计算日期到天数转换_牛客题霸_牛客网 解析代码 #include <iostream> using namespace std; int main() {int year 0, month 0, day 0, sum 0;cin >> year >> month >>…

【SpringBoot框架篇】36.整合Tess4J搭建提供图片文字识别的Web服务

文章目录 简介文件下载引入依赖main函数中使用基于Springboot搭建OCR Web服务配置traineddata路径枚举用到的语种类型定义接口响应的json数据格式封装OCR服务引擎编写web提供服务的接口启动服务并且测试html demo扩展 项目配套代码 简介 Tess4J是一个基于Tesseract OCR引擎的J…

[Java安全入门]三.URLDNS链

一.前言 在初步学习java的序列化和反序列化之后&#xff0c;这里学习java反序列化漏洞的一个利用链&#xff0c;也是比较基础的一条链。 由于URLDNS不需要依赖第三方的包&#xff0c;同时不限制jdk的版本&#xff0c;所以通常用于检测反序列化的点。 二.代码展开分析 构造链 …

Learn OpenGL 03 着色器

GLSL 着色器的开头总是要声明版本&#xff0c;接着是输入和输出变量、uniform和main函数。每个着色器的入口点都是main函数&#xff0c;在这个函数中我们处理所有的输入变量&#xff0c;并将结果输出到输出变量中。 一个典型的着色器有下面的结构&#xff1a; #version vers…

[java入门到精通] 10 常用API , 正则表达式 , Collection集合

今日目标 BigInteger类BigDecimal类Arrays类包装类String类的常用方法正则表达式Collection集合 1 BigInteger类 1.1 概述 概述 : java.math.BigInteger类是一个引用数据类型 , 可以用于计算一些大的整数 , 当超出基本数据类型数据范围的整数运算时就可以使用BigInteger了。…

Arduino Uno使用Mind+实现图形化编程

文章目录&#xff1a; 一&#xff1a;软件下载安装 1.下载安装 1.1 开发软件 2.辅助软件 2.主控板 二&#xff1a;基础 1.LED 2.传感器 3.智能小车 三&#xff1a;学习资源 一&#xff1a;软件下载安装 1.下载安装 1.1 开发软件 Arduino IDE代码编程软件&#…

集合和数组的相关操作

目录 1.数组转集合(引用类型数组) 2.数组转集合(基础类型数组) 3.集合转数组 4.集合之间是否相交 5.获取两个集合的交集 6.集合转为字符串 1.数组转集合(引用类型数组) (1)Arrays.asList 示例&#xff1a; String[] colArr new String[6];colArr[0] "1";co…