Spring AOP(面向切面编程)详解

news2024/11/28 11:00:41

文章目录

  • 前言
  • AOP 概述
    • AOP的基本概念
    • AOP 应用场景
    • AOP 常用的接口
  • Spring AOP 实现方式
    • 基于XML的配置
    • 基于注解的配置
  • Spring AOP 的使用
    • 一、Spring 的 API 接口实现AOP
    • 二、自定义类实现AOP
    • 三、注解实现AOP
  • 模拟实现 Spring AOP

前言

Spring 提供了功能强大IOC、AOP等功能,前面博主对于 Spring IoC 的内容进行了整理,给出了个人的理解,并模拟实现了 Spring IoC(完成至发现Bean注解的循环依赖),具体内容请见 HB个人博客:Spring之IOC控制反转、DI依赖注入介绍和使用(详解)、模拟实现 IoC

在软件开发过程中,我们通常需要在多个模块或功能中重复执行相同的一段代码。这些重复的代码片段通常是关注点,例如日志记录、安全检查、事务处理等。这种现象会导致代码的重复,影响项目的可维护性和可读性。为了解决这个问题,AOP(面向切面编程)应运而生。博主将详细介绍个人对 Spring AOP 相关概念、使用方法和实现原理的理解。

AOP 概述

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

​ ——摘自百度百科AOP 面向切面编程:https://baike.baidu.com/item/AOP/1332219?fromModule=lemma_inlink

AOP(Aspect Oriented Programming,面向切面编程)是一种编程思想,它的主要目的是将通用功能从业务逻辑中抽离出来,以达到代码复用和模块化的目的。

AOP的基本概念

  • 切面(Aspect):切面是一个模块化的关注点,通常包含一个或多个通知(Advice)和切入点(Pointcut)的组合。切面的作用是将横切关注点模块化,以便于重用和维护。
  • 通知(Advice):通知是切面中执行的具体操作。通知有五种类型:前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
  • 切入点(Pointcut):切入点是一个表达式,用于定义在哪些方法上应用通知。切入点可以使用通配符匹配方法名、参数类型和返回值类型。
  • 连接点(JoinPoint):连接点是程序执行过程中的某个特定的点,例如方法执行、异常抛出等。切面可以在连接点附近织入通知。
  • 织入(Weaving):织入是将切面代码插入到目标类中的过程。织入可以在编译期(Compile-time)、类加载期(Load-time)或运行期(Runtime)完成。

AOP 应用场景

  • 日志记录:可以将日志记录代码从业务逻辑代码中分离出来,使得业务逻辑更加清晰简洁。

  • 安全控制:实现安全控制策略。

  • 性能监控:实现应用程序性能监控。

  • 异常处理:实现异常处理策略。

  • 事务管理:实现事务管理策略。

  • 缓存管理:实现缓存管理策略。

AOP 常用的接口

  • JoinPoint:表示程序执行的连接点,可以获取被拦截方法的参数和返回值等信息。

  • ProceedingJoinPoint:是 JoinPoint 的一个子接口,它提供了proceed()方法,用于调用被拦截的方法。

  • Aspect:是切面的抽象,它由切点和通知组成。

  • Pointcut:切点是用于定义拦截规则的表达式,它可以匹配到程序执行的连接点。

  • Advice:通知是在切点拦截到的连接点上要执行的逻辑,包括Before、After、Around等类型。

    Spring 中支持五种类型Advice;

    通知类型连接点实现接口
    前置通知方法前org.springframework.aop.MethodBeforeAdvice
    后置通知方法后org.springframework.aop.MethodBeforeAdvice
    环绕通知方法前后org.aopalliance.intercept.MethodInterceptor
    异常抛出通知方法抛出异常org.springframework.aop.ThrowsAdvice
    引介通知类中增加新的方法org.springframework.aop.IntroductionInterceptor

    即 AOP 在不改变原有代码的情况下,去增加新的功能。

  • AopProxy:是 AOP 代理的接口,定义了获取代理对象的方法。

  • ProxyFactory:是 Spring AOP 用于创建和管理代理对象的工厂。

  • TargetSource:是目标对象的接口,定义了获取目标对象的方法。

  • Interceptor:是拦截器的接口,用于拦截方法调用并执行通知逻辑。

Spring AOP 实现方式

Spring AOP主要有两种实现方式:基于XML的配置和基于注解(@AspectJ)的配置。

基于XML的配置

首先在父 module 中导入依赖:

<dependencies>
        <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.20</version>
        </dependency>
        <!-- https://mvnrepository.com/artifact/junit/junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
</dependencies>

在子 module:aop 的 pom.xml 导入依赖:

<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
    <scope>runtime</scope>
</dependency>

基于 XML 的配置需要在 Spring 的配置文件中定义切面、通知和切入点。下面是一个基于 XML 的 Spring AOP 配置示例:

<!-- 配置目标类 -->
<bean id="userService" class="com.example.UserService"/>

<!-- 配置切面类 -->
<bean id="logAspect" class="com.example.LogAspect"/>

<!-- 配置AOP -->
<aop:config>
  <!-- 定义切面 -->
  <aop:aspect id="aspect" ref="logAspect">
    <!-- 定义切入点 -->
    <aop:pointcut id="pointcut" expression="execution(* com.example.UserService.*(..))"/>
    <!-- 定义前置通知 -->
    <aop:before method="before" pointcut-ref="pointcut"/>
    <!-- 定义后置通知 -->
    <aop:after method="after" pointcut-ref="pointcut"/>
  </aop:aspect>
</aop:config>

基于注解的配置

基于注解的配置需要在切面类上使用@Aspect注解,并在Spring配置文件中开启自动代理。下面是一个基于注解的Spring AOP配置示例:

@Aspect
public class LogAspect {

    @Before("execution(* com.example.UserService.*(..))")
    public void before() {
        System.out.println("前置通知:方法执行前");
    }

    @After("execution(* com.example.UserService.*(..))")
    public void after() {
        System.out.println("后置通知:方法执行后");
    }
}

在Spring配置文件中开启自动代理:

<aop:aspectj-autoproxy/>
<bean id="userService" class="com.example.UserService"/>
<bean id="logAspect" class="com.example.LogAspect"/>

Spring AOP 的使用

下面我们以一个简单的用户管理应用为例,演示如何使用Spring AOP实现日志记录功能。

  • 导入依赖,见上文XML配置中。

一、Spring 的 API 接口实现AOP

核心:主要是使用 Spring 的 API 接口实现

  • 创建接口 IUserService

    public interface IUserService {
        public void add();
        public void delete();
        public void update();
        public void select();
    }
    
  • 创建目标类 UserService

    public class UserService implements IUserService{
        @Override
        public void add() {
            System.out.println("增加");
        }
    
        @Override
        public void delete() {
            System.out.println("删除");
        }
    
        @Override
        public void update() {
            System.out.println("修改");
        }
    
        @Override
        public void select() {
            System.out.println("查询");
        }
    }
    
  • 创建日志类 log

    public class Log implements MethodBeforeAdvice, AfterReturningAdvice {
        /**
         * method:要执行的目标对象的方法
         * args:参数
         * target:目标对象
         */
        @Override
        public void before(Method method, Object[] args, Object target) throws Throwable {
            System.out.println("执行" + target.getClass().getName() + "的" + method.getName());
        }
    
        @Override
        public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
            System.out.println("执行了" + method.getName() + "方法,返回值为" + returnValue);
        }
    }
    
  • 创建 applicationContext.xml 配置文件

    <?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: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/aop
               http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <aop:aspectj-autoproxy/>
        <!--使用原生的 Spring API 接口-->
        <!--注册 bean-->
        <bean id="userService" class="com.hb.service.UserService"/>
        <bean id="log" class="com.hb.log.Log"/>
    
        <aop:config>
            <!--切入点:pointcut;表达式:execution(要执行的位置:正则)-->
            <aop:pointcut id="pointcut" expression="execution(* com.hb.service.UserService.*(..))"/>
    
            <!--增加环绕执行-->
            <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        </aop:config>
    </beans>
    
  • 测试以及测试结果

    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            IUserService userService = (IUserService) context.getBean("userService");
    
            userService.add();
            userService.delete();
            userService.update();
            userService.select();
        }
    }
    

    结果:

二、自定义类实现AOP

核心:主要是切面定义

  • 创建一个自定义类:

    public class Custom {
        public void before() {
            System.out.println("===执行方法前===");
        }
        public void after() {
            System.out.println("===执行方法后===");
        }
    }
    
    
  • 创建 applicationContext.xml 配置文件:

    <?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: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/aop
               http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <aop:aspectj-autoproxy/>
        <!--方式二:自定义类-->
        <bean id="custom" class="com.hb.custom.Custom"/>
        <aop:config>
            <!--自定义切面-->
            <aop:aspect ref="custom">
                <!--切入点-->
                <aop:pointcut id="point" expression="execution(* com.hb.service.UserService.*(..))"/>
                <!--通知-->
                <aop:before method="before" pointcut-ref="point"/>
                <aop:after method="after" pointcut-ref="point"/>
            </aop:aspect>
        </aop:config>
    
    </beans>
    
  • 测试以及测试结果:

    public class Test {
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            IUserService userService = (IUserService) context.getBean("userService");
    
            userService.add();
            userService.delete();
        }
    }
    

    结果:

三、注解实现AOP

核心:使用注解实现

  • 创建目标类:

    //使用注解方式实现AOP
    @Aspect //标注这个类是一个切面
    public class PointCut {
    
        @Before("execution(* com.hb.service.UserService.*(..))")
        public void before() {
            System.out.println("===执行方法前===");
        }
    
        @After("execution(* com.hb.service.UserService.*(..))")
        public void after() {
            System.out.println("===执行方法后===");
        }
    
        //在环绕通知中,可以给一个参数,代表要获取处理切入的点
        @Around("execution(* com.hb.service.UserService.*(..))")
        public void around(ProceedingJoinPoint joinPoint) throws Throwable {
            System.out.println("===环绕前===");
    
            Object proceed = joinPoint.proceed();       //执行方法
            
            System.out.println("===环绕后===");
        }
    }
    
  • 创建 applicationContext.xml 配置文件:

    <?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: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/aop
               http://www.springframework.org/schema/aop/spring-aop.xsd">
        <!--开启注解支持-->
        <aop:aspectj-autoproxy/>
    
        <bean id="userService" class="com.hb.service.UserService"/>
    	<bean id="log" class="com.hb.log.Log"/>
    
        <!--方式三:注解-->
        <bean id="pointCut" class="com.hb.byAnnotation.PointCut"/>
    
    </beans>
    
  • 测试以及测试结果:

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        IUserService userService = (IUserService) context.getBean("userService");

        userService.delete();
    }
}

运行测试类,结果如下:

模拟实现 Spring AOP

具体实现过程请见 HB个人博客:模拟实现 Spring AOP

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

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

相关文章

文心一言 VS 讯飞星火 VS chatgpt (36)-- 算法导论5.3 6题

六、请解释如何实现算法 PERMUTE-BY-SORTING&#xff0c;以处理两个或更多优先级相同的情形。也就是说&#xff0c;即使有两个或更多优先级相同&#xff0c;你的算法也应该产生一个均匀随机排列。 文心一言&#xff1a; 算法 PERMUTE-BY-SORTING 是一种基于排序的随机排列算法…

【第二章:物理层】

目录 知识框架No.0 : 引言No.1 &#xff1a;通信基础一、基本概念二、两个公式lim求极限1、失真&#xff08;失去真实性&#xff09; 三、编码和调制四、数据交换方式 No.2 &#xff1a;传输介质一、导向型介质二、非导向型介质 No.3 &#xff1a;物理层设备一、中继器二、集线…

selenium:元素定位之xpath、css

元素定位是在做UI自动化测试中最重要的一环&#xff0c;要牢牢掌握定位的方法&#xff0c;才能更有效率的进行UI自动化测试。 常见的元素定位方式&#xff1a; idnametag_nameclass_namelink_textpartial_link_textxpathcss 其中id&#xff0c;name是具有唯一性的&#xff0…

高考攀升小红书热榜!互动量破千万,品牌如何毕业季营销?

光影间&#xff0c;又是一年毕业季&#xff0c;弹指之间&#xff0c;那些青葱岁月如同白驹过隙般悄然从指缝溜走。近期&#xff0c;一年一度的高考、大学毕业又来袭&#xff0c;登上各大社媒平台热搜&#xff0c;成为热门话题&#xff1b;本期&#xff0c;随小编一起运用小红书…

【C++】智能指针 学习总结 |std::shared_ptr |std::unique_ptr | std::weak_ptr

文章目录 前言一、智能指针介绍二、普通指针和智能指针的比较案例三、std::shared_ptr四、std::unique_ptr五、std::weak_ptr六、std::shared_ptr |std::unique_ptr | std::weak_ptr三大智能指针的区别 前言 参考答案&#xff1a;chatgpt 一、智能指针介绍 智能指针是C的一种…

chatgpt赋能python:Python循环执行一个函数:简单而高效的代码实现

Python循环执行一个函数&#xff1a;简单而高效的代码实现 Python是一种高级编程语言&#xff0c;非常流行&#xff0c;不仅因为它易于学习和使用&#xff0c;而且因为它的灵活性。Python编程语言有很多特性&#xff0c;其中包括使用函数模块化编程&#xff0c;这在大型项目中…

图文验证码怎么测试及自动化测试怎么解决验证码问题?

目录 前言 首先&#xff0c;来简单认识下验证码 1、验证码的由来和作用 2、验证码的存储 3、验证码的原理 如何测试验证码&#xff1f; 1、手动测试 2、自动化测试 总结&#xff1a; 前言 在对安全性有要求的软件&#xff08;系统&#xff09;中都存在验证码&#xf…

高级查询 — 连接查询

关于northwind 示例数据库 查询数据库中的表 show tables;查询表的表属性 desc xxx(表名);连接查询 1.概述 若一个查询同时涉及两个及以上的表&#xff0c;则称之为连接查询。也可以叫做多表查询。使用join关键字进行多表连接。 2.分类&#xff08;按功能&#xff09; 内连…

二阶低通滤波器(通用滤波器)

一阶低通滤波器的详细算法介绍请参看下面文章: 一阶RC算法公式和梯形图代码 PLC信号处理系列之一阶低通(RC)滤波器算法_反馈信号低通滤波器_RXXW_Dor的博客-CSDN博客1、先看看RC滤波的优缺点 优点:采用数字滤波算法来实现动态的RC滤波,则能很好的克服模拟滤波器的缺点; 1…

学院课程功能使用说明书

引子 越来越多的讲师入驻csdn&#xff0c;也有很多学员通过学院平台进行课程学习&#xff0c;我们收到越来越多用户的反馈&#xff0c;现在通过本帖子来对课程功能做一个介绍&#xff0c;简称课程功能使用说明书1.0版&#xff0c;有任何问题均可在帖子下留言。 讲师 如何成为…

Centos7部署项目

Centos7部署项目 Centos7部署项目Centos7安装gitCentos7从github拉取代码配置git配置用于提交代码的用户名配置用户邮箱生成公钥效果如下 github添加SSH key打开你的GitHub获取ssh key公钥内容验证 拉取项目新建空白文件夹拉取项目 使用Docker Compose创建项目容器创建docker-c…

DC LAB8SDC约束四种时序路径分析

DC LAB 1.启动DC2.读入设计3. 查看所有违例的约束报告3.1 report_constraint -all_violators (alias rc)3.2 view report_constraint -all_violators -verbose -significant_digits 4 (打印详细报告) 4.查看时序报告 report_timing -significant_digits 45. 约束组合逻辑(adr_i…

1句代码,挽回1000万损失

前言&#xff1a; 在企业中&#xff0c;出于数据安全和应用高可用&#xff0c;很多软件和企业会对工程文件、数据库等做自动备份和应用容灾等。一份数据或者文件会保留到很多地方&#xff0c;虽然满足了安全性的需求&#xff0c;但是会因为保存数据区间太久造成占用大量的存储成…

chatgpt赋能python:Python快速入门神器:入门教程

Python快速入门神器&#xff1a;入门教程 Python作为一种高级的编程语言&#xff0c;在近年来逐渐成为编程语言领域里面的一匹黑马&#xff0c;在数据科学、人工智能、自动化测试和WEB开发等领域都得到了广泛的应用。Python极易入门&#xff0c;使用简单&#xff0c;代码质量较…

UnityVR--组件8--Avatar骨骼映射遮罩

什么是Avatar 模型中的人物都具有结构相似的骨架&#xff0c;如果建立一个模型骨架结构的映射&#xff0c;就能将同一个动画应用于不同的模型中&#xff0c;这就是Avatar&#xff0c;实现了一种动画重定向的功能。一般使用3DMax、Maya制作并导出为.fbx的人物模型&#xff0c;在…

【MySQL】MySQL在Linux中的环境安装与基本使用

目录 一、MySQL环境的安装 1、MySQL环境安装 2、安装MySQL出现的问题 3、登录MySQl 3.1方案一 3.2方案二 4、修改MySQL配置文件 5、可选设置&#xff1a;开机自启动MySQL&#xff08;云服务器没事一般不会关机&#xff09; 二、MySQL数据库基础 1、一些概念 2、基本…

Java中进制转换的两种方法你知道吗?

目录 十进制转其他进制 其他进制转十进制 实战&#xff1a; A进制转B进制 关于大数运算可以参考躲不掉的高精度计算&#xff0c;蓝桥杯必考_高精度算法在哪些比赛考_无忧#的博客-CSDN博客 十进制转其他进制 使用 Integer.toString(int n,int radix) 方法&#xff0c;该方法…

【LeetCode】739. 每日温度

739. 每日温度&#xff08;中等&#xff09; 思路 我们可以维持一个单调递减的栈&#xff0c;表示每天的温度&#xff1b;为了方便计算天数差&#xff0c;这里存放位置&#xff08;下标&#xff09;&#xff0c;而非温度本身。因为温度可以通过访问数组下标获取。 从左向右遍历…

书接上文,基于藏文手写数字数据开发构建yolov5n轻量级藏文手写数字检测识别系统

在上一篇文章中&#xff1a;《python基于轻量级CNN模型开发构建手写藏文数字识别系统》 开发实现了轻量级的藏文手写数字识别系统&#xff0c;这里主要是想基于前文的数据&#xff0c;整合目标检测模型来进一步挖掘藏文手写数字数据集的可玩性&#xff0c;基于yolov5n开发构建…

Android kotlin序列化之@Parcelize详解与使用

一、介绍 在Android开发过程中&#xff0c;序列化使用概率一直很高。在页面之间传递的对象&#xff0c;需要要使用序列化&#xff0c;常见的序列化&#xff1a;Parcelable、Serialization。 由于Parcelable在传递压缩比高&#xff0c;效率高&#xff0c;一直被Google官方推荐。…