图文详解Spring AOP,你学会了吗?

article/2025/9/14 9:36:52

如果说 IOC 是 Spring 的核心,那么面向切面编程AOP就是 Spring 另外一个最为重要的核心,需要重点掌握@mikechen

本篇主要会详解以下六点:

1.AOP的定义

2.AOP的作用

3.AOP的应用场景

4.Spring AOP的术语

  • AOP核心概念
  • Spring AOP 通知分类
  • Spring AOP 织入时期

5.Spring AOP三种使用方式

  1. 方式1:使用Spring自带的AOP
  2. 方式2:使用Aspectj实现切面(普通POJO的实现方式)
  3. 方式3:使用Aspectj实现切面(基于注解的实现方式)

6.Spring AOP的实现原理

  1. JDK动态代理
  2. JDK动态代理优缺
  3. CGLib代理
  4. CGLIB组成结构

AOP的定义

AOP (Aspect Orient Programming),直译过来就是 面向切面编程,AOP 是一种编程思想,是面向对象编程(OOP)的一种补充。

面向切面编程,实现在不修改源代码的情况下给程序动态统一添加额外功能的一种技术,如下图所示:

AOP可以拦截指定的方法并且对方法增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离,比如Spring的事务,通过事务的注解配置,Spring会自动在业务方法中开启、提交业务,并且在业务处理失败时,执行相应的回滚策略。

AOP的作用

AOP 采取横向抽取机制(动态代理),取代了传统纵向继承机制的重复性代码,其应用主要体现在事务处理、日志管理、权限控制、异常处理等方面。

主要作用是分离功能性需求和非功能性需求,使开发人员可以集中处理某一个关注点或者横切逻辑,减少对业务代码的侵入,增强代码的可读性和可维护性。

简单的说,AOP 的作用就是保证开发者在不修改源代码的前提下,为系统中的业务组件添加某种通用功能。

AOP的应用场景

比如典型的AOP的应用场景:

  • 日志记录
  • 事务管理
  • 权限验证
  • 性能监测

AOP可以拦截指定的方法,并且对方法增强,比如:事务、日志、权限、性能监测等增强,而且无需侵入到业务代码中,使业务与非业务处理逻辑分离。

Spring AOP的术语

在深入学习SpringAOP 之前,让我们先对AOP的几个基本术语有个大致的概念。

AOP核心概念

Spring AOP 通知分类

Spring AOP 织入时期

Spring AOP三种使用方式

AOP编程其实是很简单的事情,纵观AOP编程,程序员只需要参与三个部分:

1.定义普通业务组件

2.定义切入点,一个切入点可能横切多个业务组件

3.定义增强处理,增强处理就是在AOP框架为普通业务组件织入的处理动作

所以进行AOP编程的关键就是定义切入点和定义增强处理,一旦定义了合适的切入点和增强处理,AOP框架将自动生成AOP代理,即:代理对象的方法=增强处理+被代理对象的方法。

方式1:使用Spring自带的AOP

public class LogAdvice implements MethodBeforeAdvice, AfterReturningAdvice,MethodInterceptor {@Overridepublic void before(Method method, Object[] objects, Object target) throws Throwable {//前置通知}@Overridepublic void afterReturning(Object result, Method method, Object[] objects, Object target) throws Throwable {//后置通知}@Overridepublic Object invoke(MethodInvocation methodInvocation) throws Throwable {//环绕通知//目标方法之前执行methodInvocation.proceed();   //目标方法//目标方法之后执行return resultVal;}}

配置通知时需实现org.springframework.aop包下的一些接口

  • 前置通知:MethodBeforeAdvice
  • 后置通知:AfterReturningAdvice
  • 环绕通知:MethodInterceptor
  • 异常通知:ThrowsAdvice

创建被代理对象

<bean id="orderServiceBean" class="com.apesource.service.impl.OrderServiceImpl"/><bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/>

通知(Advice)

<bean id="logAdviceBean" class="com.apesource.log.LogAdvice"/><bean id="performanceAdviceBean" class="com.apesource.log.PerformanceAdvice"/>

切入点(Pointcut):通过正则表达式描述指定切入点(某些 指定方法)

<bean id="createMethodPointcutBean"         class="org.springframework.aop.support.JdkRegexpMethodPointcut"><!--注入正则表达式:描述那些方法为切入点--><property name="pattern" value=".*creat.*"/></bean>

Advisor(高级通知) = Advice(通知) + Pointcut(切入点)

<bean id="performanceAdvisorBean" class="org.springframework.aop.support.DefaultPointcutAdvisor"><!--注入切入点--><property name="pointcut" ref="createMethodPointcutBean"/><!--注入通知--><property name="advice" ref="performanceAdviceBean"/></bean>

创建自动代理

<bean class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator"><!--Bean名称规则(数组):指定那些bean创建自动代理--><property name="beanNames"><list><value>*ServiceBean</value><value>*TaskBean</value></list></property><!--通知列表:需要执行那些通知--><property name="interceptorNames"><list><value>logAdviceBean</value><value>performanceAdvisorBean</value></list></property></bean>

方式2:使用Aspectj实现切面(普通POJO的实现方式)

导入Aspectj相关依赖

<!--aop依赖1:aspectjrt --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjrt</artifactId><version>1.9.5</version></dependency><!--aop依赖2:aspectjweaver --><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.5</version></dependency>

通知方法名随便起,没有限制

public class LogAspectj {//前置通知public void beforeAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj前置通知】 ==========");}//后置通知:方法正常执行后,有返回值,执行该后置通知:如果该方法执行出现异常,则不执行该后置通知public void afterReturningAdvice(JoinPoint joinPoint,Object returnVal){System.out.println("========== 【Aspectj后置通知】 ==========");}public void afterAdvice(JoinPoint joinPoint){System.out.println("========== 【Aspectj后置通知】 ==========");}//环绕通知public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {System.out.println("##########【环绕通知中的前置通知】##########");Object returnVale = joinPoint.proceed();System.out.println("##########【环绕通知中的后置通知】##########");return returnVale;}/*** 异常通知:方法出现异常时,执行该通知*/public void throwAdvice(JoinPoint joinPoint, Exception ex){System.out.println("出现异常:" + ex.getMessage());}}

使用Aspectj实现切面,使用Spring AOP进行配置

方式3:使用Aspectj实现切面(基于注解的实现方式)

<!--业务组件bean--><bean id="userServiceBean" class="com.apesource.service.impl.UserServiceImpl"/><!--日志Aspect切面--><bean id="logAspectjBean" class="com.apesource.log.LogAspectj"/><!--使用Aspectj实现切面,使用Spring AOP进行配置--><aop:config><!--配置切面--><!--注入切面bean--><aop:aspect ref="logAspectjBean"><!--定义Pointcut:通过expression表达式,来查找 特定的方法(pointcut)--><aop:pointcut id="pointcut"expression="execution(* com.apesource.service.impl.*.create*(..))"/><!--配置"前置通知"--><!--在pointcut切入点(serviceMethodPointcut)查找到 的方法执行"前",来执行当前logAspectBean的doBefore--><aop:before method="beforeAdvice" pointcut-ref="pointcut"/><!--配置“后置通知”--><!--returning属性:配置当前方法中用来接收返回值的参数名--><aop:after-returning returning="returnVal" method="afterReturningAdvice" pointcut-ref="pointcut"/> <aop:after method="afterAdvice" pointcut-ref="pointcut"/><!--配置"环绕通知"--><aop:around method="aroundAdvice" pointcut-ref="pointcut"/><!--配置“异常通知”--><!--throwing属性:配置当前方法中用来接收当前异常的参数名--><aop:after-throwing throwing="ex" method="throwAdvice" pointcut-ref="pointcut"/></aop:aspect></aop:config>

Spring AOP的实现原理

Spring的AOP实现原理其实很简单,就是通过动态代理实现的。

Spring AOP 采用了两种混合的实现方式:JDK 动态代理和 CGLib 动态代理。

  • JDK动态代理:Spring AOP的首选方法。每当目标对象实现一个接口时,就会使用JDK动态代理。目标对象必须实现接口
  • CGLIB代理:如果目标对象没有实现接口,则可以使用CGLIB代理。

JDK动态代理

Spring默认使用JDK的动态代理实现AOP,类如果实现了接口,Spring就会使用这种方式实现动态代理。

JDK实现动态代理需要两个组件,首先第一个就是InvocationHandler接口。

我们在使用JDK的动态代理时,需要编写一个类,去实现这个接口,然后重写invoke方法,这个方法其实就是我们提供的代理方法。

如下源码所示:

/*** 动态代理** @author mikechen*/public class JdkProxySubject implements InvocationHandler {private Subject subject;public JdkProxySubject(Subject subject) {this.subject = subject;}@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {System.out.println("before 前置通知");Object result = null;try {result = method.invoke(subject, args);}catch (Exception ex) {System.out.println("ex: " + ex.getMessage());throw ex;}finally {System.out.println("after 后置通知");}return result;}}

然后JDK动态代理需要使用的第二个组件就是Proxy这个类,我们可以通过这个类的newProxyInstance方法,返回一个代理对象。

生成的代理类实现了原来那个类的所有接口,并对接口的方法进行了代理,我们通过代理对象调用这些方法时,底层将通过反射,调用我们实现的invoke方法。

public class Main { public static void main(String[] args) { //获取InvocationHandler对象 在构造方法中注入目标对象InvocationHandler handler = new JdkProxySubject(new RealSubject()); //获取代理类对象Subject proxySubject = (Subject)Proxy.newProxyInstance(Main.class.getClassLoader(), new Class[]{Subject.class}, handler); //调用目标方法proxySubject.request(); proxySubject.response();}

运行结果:

before 前置通知执行目标对象的request方法......after 后置通知before 前置通知执行目标对象的response方法......after 后置通知

JDK动态代理优缺

优点

JDK动态代理是JDK原生的,不需要任何依赖即可使用;

通过反射机制生成代理类的速度要比CGLib操作字节码生成代理类的速度更快;

缺点

如果要使用JDK动态代理,被代理的类必须实现了接口,否则无法代理;

JDK动态代理无法为没有在接口中定义的方法实现代理,假设我们有一个实现了接口的类,我们为它的一个不属于接口中的方法配置了切面,Spring仍然会使用JDK的动态代理,但是由于配置了切面的方法不属于接口,为这个方法配置的切面将不会被织入。

JDK动态代理执行代理方法时,需要通过反射机制进行回调,此时方法执行的效率比较低;

CGLib代理

CGLIB组成结构

Cglib是一个强大的、高性能的代码生成包,它广泛被许多AOP框架使用,为他们提供方法的拦截,如下图所示Cglib与Spring等应用的关系:

  • 最底层的是字节码Bytecode,字节码是Java为了保证“一次编译、到处运行”而产生的一种虚拟指令格式,例如iload_0、iconst_1、if_icmpne、dup等
  • 位于字节码之上的是ASM,这是一种直接操作字节码的框架,应用ASM需要对Java字节码、Class结构比较熟悉
  • 位于ASM之上的是CGLIB、Groovy、BeanShell,后两种并不是Java体系中的内容而是脚本语言,它们通过ASM框架生成字节码变相执行Java代码,这说明在JVM中执行程序并不一定非要写Java代码—-只要你能生成Java字节码,JVM并不关心字节码的来源,当然通过Java代码生成的JVM字节码是通过编译器直接生成的,算是最“正统”的JVM字节码
  • 位于CGLIB、Groovy、BeanShell之上的就是Hibernate、Spring AOP这些框架了,这一层大家都比较熟悉
  • 最上层的是Applications,即具体应用,一般都是一个Web项目或者本地跑一个程序

所以,Cglib的实现是在字节码的基础上的,并且使用了开源的ASM读取字节码,对类实现增强功能的。

如果感觉小编写得不错,请素质三连:点赞+转发+关注。我会努力写出更好的作品分享给大家。更多JAVA进阶学习资料小编已打包好,可以关注私信找我领取哦!


http://chatgpt.dhexx.cn/article/R4hQ8DZY.shtml

相关文章

Spring AOP全面详解(超级详细)

如果说IOC 是 Spring 的核心&#xff0c;那么面向切面编程AOP就是 Spring 另外一个最为重要的核心mikechen AOP的定义 AOP &#xff08;Aspect Orient Programming&#xff09;,直译过来就是 面向切面编程,AOP 是一种编程思想&#xff0c;是面向对象编程&#xff08;OOP&…

mysql执行SQL脚本

方法一 【Mysql的bin目录】\mysql –u用户名 –p密码 –D数据库<【sql脚本文件路径全名】 示例&#xff1a; 如果mysql配了全局变量&#xff0c;就不需要到Mysql的bin目录下执行&#xff0c;可以在任何地方使用用户名、密码、指定数据库等参数值与参数名不需要隔空格 不…

SpringBoot 实现SQL脚本自动执行

SpringBoot 实现配置SQL脚本自动执行 一. 背景 我们可能遇到过这种情况: 在公网开发时, 新增数据表非常容易, 直接登录到对应服务器的mysql / 使用Navicat访问mysql服务器. 然后去执行sql语句或脚本即可在内网开发时, 由于都在一个网段, 所以操作也比较方便但是在公网开发, 部…

flink-sql-client提交sql脚本文件

标题: flink-sql-client提交sql脚本文件 日期: 2021-10-22 22:11:34 标签: [flink,sql-client] 分类: flink 我们知道&#xff0c;sql-client.sh可以提供给我们一个sql交互界面&#xff0c;让我们没执行一个sql&#xff0c;就可以看到执行结果&#xff0c;也可以交互式查询表的…

如何在mysql中执行sql脚本文件

一、sql脚本文件 简介 xxxx.sql这种文件被称为sql脚本文件。sql脚本文件中编写了大量的sql语句。我们执行sql脚本文件的时候&#xff0c;该文件中所有的sql语句会全部执行&#xff01;批量的执行SQL语句&#xff0c;可以使用sql脚本文件。 上面这个vip.sql就是sql脚本文件&am…

使用sql脚本创建数据库表

准备脚本语句&#xff1a; CREATE TABLE test (title varchar(100) DEFAULT NULL,author varchar(10) DEFAULT NULL,digest varchar(255) DEFAULT NULL,content text,content_source_url varchar(500) DEFAULT NULL,thumb_media_id varchar(255) DEFAULT NULL,need_open_comme…

PowerDesigner生成Sql脚本

点击工具栏上的“Database”&#xff0c;选择“Change Current DBMS”进行修改导出脚本类型&#xff0c;可以选择mysql、sql server/ oracle 、db2等主流的数据库。 在DBMS中点击下拉菜单&#xff0c;选择要导出的数据库脚本&#xff0c;对名字进行自定义&#xff0c;点击确定即…

PowerDesigner导入sql脚本

一个好的数据库建模,不但可以让人直观的理解模型,充分的利用数据库技术,优化数据库的设计,而且还可以让新员工快速的熟悉数据库表结构与业务之间的关系.无奈的是随着开发过程中,数据库表结构字段的增删以及关联关系的变动给数据库模型带来维护上的巨大工作量.现为了维护上的简单…

dbeaver导入sql脚本

新建数据库 执行脚本 选择脚本文件 选择mysql 然后按确定就行了

springboot + mybatis启动时执行sql脚本

目录 1. 创建数据版本表&#xff0c;结构如下&#xff1a; 2. 创建HdVersion对象 3. 创建执行sql的dao 4. 创建dao对应的xml 5.创建sql执行器&#xff0c;实现ApplicationRunner 6. 结语 背景&#xff1a;项目开发或发布阶段修改表结构&#xff0c;项目更新时需要手动执行脚…

SpringBoot启动自动执行sql脚本

在开发当中我们每次发布服务都需要手动执行脚本&#xff0c;然后重启服务&#xff0c;而SpringBoot有服务启动自动执行sql脚本的功能的&#xff0c;可以为我们省去手动执行脚本的这一步&#xff0c;只需要部署新的服务即可。 这个功能是SpringBoot自带的不需要引入额外的依赖&a…

Excel数据转化为sql脚本

在实际项目开发中&#xff0c;有时会遇到客户让我们把大量Excel数据导入数据库的情况。这时我们就可以通过将Excel数据转化为sql脚本来批量导入数据库。 1 在数据前插入一列单元格&#xff0c;用来拼写sql语句。 具体写法&#xff1a;"insert into t_student (id,name,ag…

MySQL导出sql脚本文件

⭐️前言⭐️ sql脚本文件在我们做项目时&#xff0c;特别是学习别人的开源项目时经常需要进行导入导出操作&#xff0c;才能在自己的系统上跑起来&#xff0c;这篇文章主要介绍如何导出sql脚本文件&#xff0c;具体操作如下&#xff0c;附带截图详解。 &#x1f349;博客主页…

dataGrip导出sql脚本

1.打开dataGrip。 2.选择要导出的数据库表。 3.点击右键->选择"Dump Data to File(s)", 同时选择&#xff0c;Skip Computed Columns(sql),Add Table Definition(sql),Overwrite Exsting Files和Single File。 4.点击sql Inserts 5.选择文件保存位置 6.生成sql脚…

linux下plsql怎么执行sql脚本,plsql怎么执行sql脚本

首先,我们需要登录需要执行sql文件的用户,在我们确保sql文件无误的情况下,进入plsqldeveloper: 1,找到tools---》import tables ---》选择sql insert,不要选中sqlplus,选择最下面的那个导入sql文件,选中好sql文件后,点击import就会执行sql语句,生成日志。 2,如果执行…

DBeaver执行SQL脚本文件

1、右键库名&#xff0c;点击工具-->执行脚本 2、在弹出窗口中选择输入文件&#xff0c;并修改Extra command args:--default-character-setutf8&#xff0c;防止中文乱码&#xff0c;点击开始按钮。 3、执行完成。

kettle执行SQL脚本

参考一下kettle官方文档 kettle什么时候需要创建临时表呢 SELECT * WHERE cid IN(xxx) 的数据太多&#xff0c;占了很大内存。 目标表有没有必要做逻辑删除&#xff0c;如果做逻辑删除&#xff0c;后期数据量增长过快。 目标表增量更新&#xff1a;1、sql直接插入&#xff1…

SQL Server SQL脚本

本节的主要内容是要教大家怎么通过编写 SQL 脚本来查询、更新并且运行数据库。 利用 SQL 脚本我们能做很多事情&#xff0c;比如插入数据、读取数据、更新数据以及删除数据等&#xff1b;它们也可以用于创建数据库对象&#xff0c;如表&#xff0c;视图&#xff0c;存储过程&a…

SQL Server 数据库之生成与执行 SQL 脚本

生成与执行 SQL 脚本 1. 将数据库生成2. 将数据表生成 SQL 脚本4. 执行 SQL 脚本 1. 将数据库生成 使用对象资源管理器能快速创建整个数据的脚本&#xff0c;也能使用默认选项创建单个数据库对象的脚本&#xff1b; 用户能在查询编辑器窗口中对文件或剪贴板创建脚本&#xff0…

SQL 常用脚本大全

1、行转列的用法PIVOT CREATE table test (id int,name nvarchar(20),quarter int,number int) insert into test values(1,N苹果,1,1000) insert into test values(1,N苹果,2,2000) insert into test values(1,N苹果,3,4000) insert into test values(1,N苹果,4,5000) insert …