SpringAOP详细配置与使用

article/2025/9/14 9:39:25

目录

SpringAOP简介

AOP概念

Spring AOP简单流程图

Spring AOP之Annotation

前置通知(Before advice)

返回后通知(After reurning advice)

抛出异常后通知(After throwing advice)

后置通知(After (finally) advice)

环绕通知(Around advice)

引入(Introduction)

SpringAOP之XML

AOP日志实现

参考文献


SpringAOP简介

    面向切面编程(Aspect Oriented Programming)提供了另一种角度来思考程序的结构,通过这种方式弥补面向对象编程(Object Oriented Programming)的不足。除了类以外,AOP提供了切面,切面对关注点进行模块化,例如横切多个类型和对象的事务管理(这些关注点术语通常称作横切(crosscutting)关注点)。Spring AOP是Spring的一个重要组件,但是Spring IOC并不依赖于Spring AOP,这意味着你可以自由选择是否使用AOP,AOP提供了强大的中间件解决方案,这使得Spring IOC更加完善。我们可以通过AOP来实现日志监听,事务管理,权限控制等等。

AOP概念

  • 切面(Aspect):一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是Java应用程序中一个关于横切关注点的很好的例子。在Spring AOP中,切面可以使用通过类(基于模式(XML)的风格)或者在普通类中以@Aspect注解(AspectJ风格)来实现。
  • 连接点(Join point):程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。在Spring AOP中一个连接点总是代表一个方法的执行。个人理解:AOP拦截到的方法就是一个连接点。通过声明一个org.aspectj.lang.JoinPoint类型参数我们可以在通知(Advice)中获得连接点的信息。这个在稍后会给出案例。
  • 通知(Advice):在切面(Aspect)的某个特定连接点上(Join point)执行的动作。通知的类型包括"around","before","after"等等。通知的类型将在后面进行讨论。许多AOP框架,包括Spring 都是以拦截器作为通知的模型,并维护一个以连接点为中心的拦截器链。总之就是AOP对连接点的处理通过通知来执行。个人理解:Advice指当一个方法被AOP拦截到的时候要执行的代码。
  • 切入点(Pointcut):匹配连接点(Join point)的断言。通知(Advice)跟切入点表达式关联,并在与切入点匹配的任何连接点上面运行。切入点表达式如何跟连接点匹配是AOP的核心,Spring默认使用AspectJ作为切入点语法。个人理解:通过切入点的表达式来确定哪些方法要被AOP拦截,之后这些被拦截的方法会执行相对应的Advice代码。
  • 引入(Introduction):声明额外的方法或字段。Spring AOP允许你向任何被通知(Advice)对象引入一个新的接口(及其实现类)。个人理解:AOP允许在运行时动态的向代理对象实现新的接口来完成一些额外的功能并且不影响现有对象的功能。
  • 目标对象(Target object):被一个或多个切面(Aspect)所通知(Advice)的对象,也称作被通知对象。由于Spring AOP是通过运行时代理实现的,所以这个对象永远是被代理对象。个人理解:所有的对象在AOP中都会生成一个代理类,AOP整个过程都是针对代理类在进行处理。
  • AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能),在Spring中AOP可以是JDK动态代理或者是CGLIB代理。
  • 织入(Weaving):把切面(aspect)连接到其他的应用程序类型或者对象上,并创建一个被通知对象。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。Spring和其他纯AOP框架一样,在运行时完成织入。个人理解:把切面跟对象关联并创建该对象的代理对象的过程。

通知(Advice)的类型:

  • 前置通知(Before advice):在某个连接点(Join point)之前执行的通知,但这个通知不能阻止连接点的执行(除非它抛出一个异常)。
  • 返回后通知(After returning advice):在某个连接点(Join point)正常完成后执行的通知。例如,一个方法没有抛出任何异常正常返回。
  • 抛出异常后通知(After throwing advice):在方法抛出异常后执行的通知。
  • 后置通知(After(finally)advice):当某个连接点(Join point)退出的时候执行的通知(不论是正常返回还是发生异常退出)。
  • 环绕通知(Around advice):包围一个连接点(Join point)的通知,如方法调用。这是最强大的一种通知类型。环绕通知可以在方法前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。

Spring AOP简单流程图

    上图为我个人对AOP流程的一个理解。我把面向对象的过程从一个HttpRquest到访问数据库DB的整个流程看做是一条直线。AOP定义了一个切面(Aspect),一个切面包含了切入点,通知,引入,这个切面上定义了许多的切入点(Pointcut),一旦访问过程中有对象的方法跟切入点匹配那么就会被AOP拦截。此时该对象就是目标对象(Target Object)而匹配的方法就是连接点(Join Point)。紧接着AOP会用过JDK动态代理或者CGLIB生成一个目标对象的代理对象(AOP proxy),这个过程就是织入(Weaving)。这个时候我们就可以按照我们的需求对连接点进行一些拦截处理。可以看到,我们可以引入(Introduction)一个新的接口,让代理对象来实现这个接口来,以实现额外的方法和字段。也可以在连接点上进行通知(Advice),通知的类型包括了前置通知,返回后通知,抛出异常后通知,后置通知,环绕通知。最后也是最骚的是整个过程不会改变代码原有的逻辑。下面我将通过简单的案例对Spring AOP进行演示,过程会包括XML以及Annotation。实例结构基本跟基于SpringMVC+Spring+Hibernate+Maven+Bootstrap的简单Demo以及SpringMVC整合Mybatis+Maven+Bootstrap的简单Demo一致不一样的地方我会贴出代码。

Spring AOP之Annotation

    首先在bean.xml文件中添加<aop:aspectj-autoproxy />开启Spring对@Aspect的支持。

       <context:component-scan base-package="com.ctc" />//IOC自动扫包<aop:aspectj-autoproxy />//使用AOP注解

    声明一个切面,在类UserAspect上加上@Aspect注解。并定义了两个切入点addLog()以及skipMethod()。Spring主要使用的execetion来匹配连接点。此外还有within,this,target等等,这边不再解释有需要可以参考官方文档。此外Spring文档要求定义切入点(Pointcut)的方法的返回值必须的void类型。但是我自己测试了下其他返回类型,还是可以正常使用。不知道是不是因为测试环境的原因,总之就按照官方的来吧。

@Aspect
public class UserAspect {//匹配所有ServiceImpl包下面的所有类的所有方法@Pointcut("execution(* com.ctc.ServiceImpl.*.*(..))")public void addLog(){}//@Pointcut("execution(public * *(..))")//public void skipMethod(){}}

    接着定义一个Advice类在上面加上@Aspect注解,同时必须在类上添加注解@Component否则Spring就扫描不到这个类。下面将对5种通知类型以及一个引入(Introduction)AgeGroup进行演示。

@Aspect
@Component
public class LogAdvice {/*	@DeclareParents(value="com.ctc.ServiceImpl.*+",defaultImpl=Adult.class)public static AgeGroup ageGroup;*///所有的通知都可以使用这种方式,直接把Pointcut跟Advice连接起来,但是为了更好的理解前文的概念以及图片,这边分开定义。//@Before("execution(* com.ctc.ServiceImpl.*.*(..))");@Before("com.ctc.AspectJ.UserAspect.addLog()")public void before(){System.out.println("LogAdvice before advice ");}/*	@AfterReturning("com.ctc.AspectJ.UserAspect.addLog()")public void AfterReturning(){System.out.println("LogAdvice after returning advice ");}@AfterThrowing("com.ctc.AspectJ.UserAspect.addLog()")public void AfterThrowing(){System.out.println("LogAdvice after throwing advice ");}@After("com.ctc.AspectJ.UserAspect.addLog()")public void After(){System.out.println("LogAdvice after advice ");}//除了可以通过名字来指向对应的切入点表达式,还可以可以使用'&&', '||' 和 '!'来合并。//切入点表达式的 args(user,..) 表示某个与切入表达式匹配的连接点它把User对象作为第一个参数,通过这个语法我们可以在通知中访问到这个User对象。@Around("com.ctc.AspectJ.UserAspect.addLog()&&" +"args(user,..)")public void around(ProceedingJoinPoint  joinPoint,User user) throws Throwable{System.out.println("log begin!");System.out.println("log end");}*/}
	@Testpublic void Aspect(){User user = new User();user.setUserName("test");user.setPassword("123");UserService userService = (UserService) cx.getBean("userServiceImpl");userService.addUser(user);
/*		AgeGroup userAdult = (AgeGroup) cx.getBean("userServiceImpl");userAdult.isAdult();System.out.println(userAdult instanceof UserService);*/}//UserServiceImpl中的方法@Overridepublic void addUser(User user) {System.out.println("add into DB");} 

前置通知(Before advice)

返回后通知(After reurning advice)

    如果方法抛出异常,那么返回后通知就不会执行:

抛出异常后通知(After throwing advice)

   

后置通知(After (finally) advice)

   

把userDaoImpl = null删掉。

 

环绕通知(Around advice)

    环绕通知是一种功能比较强大的通知类型,它可以把第一个参数定义为org.aspectj.lang.Joinpoint类型(环绕通知需要定义为ProcessJoinPoint类型,它是JoinPoint的一个子类)。通过ProcessJoinPoint可以调用process()决定是否让连接点(Join point)继续执行,或者是调用getArgs()返回方法参数,getThis()返回代理对象,getTarget()返回目标对象,getSignature()返回正在被通知的方法的相关信息和toString()打印出正在被通知的方法的有用信息。

下面将从四种情况进行演示:

第一种在方法执行前后添加通知:

第二种方法不执行:

第三种抛出异常停止执行:

第四种返回方法的返回值:

       //因为原有的方法为void就是一个空值,这边改用String方便测试。@Overridepublic String addUser(User user) {userDaoImpl.addUser(user);return "add success!";}

引入(Introduction)

    引入在AspectJ中被称为inter-tye声明,它可以使代理对象实现一个给定的接口用来添加额外的方法或字段。在下面案例中,我们首先引入一个新的接口以及接口的实现类,然后再通过@DeclareParents来定义一个引入,其中value表示要引入的目标对象,defaultImpl表示要实现接口的实现类的Class对象。

public interface AgeGroup {public void isAdult();}public class Adult implements AgeGroup {@Overridepublic void isAdult() {System.out.println("Yes,he is an adult.");}}

SpringAOP之XML

    XML的实例就是把前面用Annotation注解的方式转到配置文件中,案例代码不变并且只给出一个案例,不全部还原。

 //为了方便,只保留了一个before用来演示。public class LogAdvice {public void before(){System.out.println("LogAdvice before advice ");}}

  在选择XML还是Annotation上面,XML的配置是Spring用户最熟悉的,可以很清楚的从配置文件中了解到AOP的应用。但是它的缺点在于XML能够支持的功能会比Annotation的方式差一点,例如在注解上面,我们支持两个切入点表达式进行组合。而XML的方式无法做到。而且通过Annotation的方式如果有需要的话可以很容易的移植到AspectJ上,所以Spring团队更喜欢用Annotation的方式。总之仁者见仁智者见智,看需要吧。

AOP日志实现

  AOP能够实现的事情比较多,此处给出如何通过aop进行日志处理,包括方法调用时长,日志链添加,前置通知等。(注:项目中实现日志的方式有很多种。)

@Aspect
@Component
public class LogAdvice {private final Logger logger = LoggerFactory.getLogger(this.getClass());/*** 横切所有controller下的public方法*/@Pointcut("execution(public * com.ctc.controller.*.*(..))")public void controllerPointCut(){}/*** 横切所有service下的public方法*/@Pointcut("execution(public * com.ctc.service.*.*(..))")public void servicePointCut(){}/*** 环绕通知* @param joinPoint* @return* @throws Throwable*/@Around("controllerPointCut()")public BaseResponse aroundController(ProceedingJoinPoint  joinPoint) throws Throwable{String uuid = UUID.randomUUID().toString().replace("-", "");// 添加日志链MDC.put("mdcId", uuid);// 获取Controller入参Object[] objects = joinPoint.getArgs();String request = "";for (Object o : objects) {if (o instanceof BaseRequest) {request = o.toString();break;}}String methodName = joinPoint.getSignature().getName();logger.info("请求开始: methodName = {}, request = {}", methodName, request);long startTime = System.currentTimeMillis();BaseResponse response = (BaseResponse) joinPoint.proceed();long endTime = System.currentTimeMillis();long executeTime = endTime - startTime;logger.info("请求结束: methodName = {}, result = {}, 执行时间: time = {}ms", methodName, response, executeTime);if (MDC.get("reqId") != null) {// 请求结束后移除日志链MDC.remove("reqId");}return response;}/*** 前置通知业务层*/@Before("servicePointCut()" )public void aroundService(JoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();String[] names = methodSignature.getParameterNames();// 获取Service入参Object[] objects = joinPoint.getArgs();StringBuilder sb = new StringBuilder();for (int i = 0; i < objects.length; i++) {sb.append(names[i] + " = " + objects[i]);if (i != objects.length) {sb.append(", ");}}String methodName = methodSignature.getName();logger.info("执行方法:{}; {}", methodName, sb.toString());}
}
@RestController
@RequestMapping("/test")
public class TestController {@Autowiredprivate TestService testService;@GetMapping("/hello")public BaseResponse getInfo() {return ResponseUtil.getSuccessResponse();}@PostMapping("/post")public BaseResponse getInfo2(@RequestBody ProductInfoRequest request) {testService.test1();testService.test2(1, "adff", request);return ResponseUtil.getSuccessResponse(request);}
}@Service
public class TestServiceImpl implements TestService {@Overridepublic void test1() {test3();}@Overridepublic void test2(int i, String s, BaseRequest request) {}// 该示例中test3不会被横切到,因为其方法修饰符为privateprivate void test3() {}
}

执行结果:

--2019-08-03 14:01:59.631 - INFO 7624 --- [6d4848cd552c46c5bcbd8d083f30c2d4] com.ctc.aspect.LogAdvice    : 请求开始: methodName = getInfo, request = 
--2019-08-03 14:01:59.631 - INFO 7624 --- [6d4848cd552c46c5bcbd8d083f30c2d4] com.ctc.aspect.LogAdvice    : 请求结束: methodName = getInfo, result = BaseResponse(code=0, data=null, msg=success), 执行时间: time = 0ms
--2019-08-03 14:03:00.670 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 请求开始: methodName = getInfo2, request = ProductInfoRequest(id=1, name=haha)
--2019-08-03 14:03:00.671 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 执行方法:test1; 
--2019-08-03 14:03:00.675 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 执行方法:test2; i = 1, s = adff, request = ProductInfoRequest(id=1, name=haha), 
--2019-08-03 14:03:00.675 - INFO 7624 --- [799d9b6a1c27498b8ab765f5b409c146] com.ctc.aspect.LogAdvice    : 请求结束: methodName = getInfo2, result = BaseResponse(code=0, data=ProductInfoRequest(id=1, name=haha), msg=success), 执行时间: time = 4ms

参考文献

https://docs.spring.io/spring/docs/4.3.25.RELEASE/spring-framework-reference/htmlsingle/

 


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

相关文章

SpringAOP的注解形式

铁子们&#xff0c;快扫码关注啦&#xff01;或 wx搜索&#xff1a;“聊5毛钱的java”&#xff0c;关注可领取博主的Java学习视频资料&#xff0c;保证都是干货 上一篇讲了配置文件形式的SpringAOP&#xff1a;Spring中的AOP以及切入点表达式和各种通知 本篇继续看一下注解形…

Spring AOP超详细解析

AOP - 面向切面编程&#xff08;Aspect Oriented Programming&#xff09; Spring早期版本的核心功能&#xff1a;管理对象生命周期与对象分配。 即Bean本身的管理创建&#xff0c;以及它整个生命周期里跟其他对象相互之间引用装配 为了更好的实现管理和装配&#xff0c;一个…

Spring学习:AOP概述

一、AOP概念 AOP是指面向切面编程&#xff0c;利用 AOP 可以对业务逻辑的各个部分进行隔离&#xff0c;从而使得业务逻辑各部分之间的耦合度降低&#xff0c;提高程序的可重用性&#xff0c;同时提高了开发的效率。 通俗描述&#xff1a;不通过修改源代码方式&#xff0c;在主干…

SpringAOP学习--SpringAOP简介及原理

前文对AOP做了介绍&#xff0c;实际项目中&#xff0c;一般不会直接上手手动实现aop&#xff0c;而是使用一些高级封装的aop实现&#xff0c;如SpringAOP。 Spring是一个广泛应用的框架&#xff0c;SpringAOP则是Spring提供的一个标准易用的aop框架&#xff0c;依托Spring的IOC…

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

如果说 IOC 是 Spring 的核心&#xff0c;那么面向切面编程AOP就是 Spring 另外一个最为重要的核心&#xff0c;需要重点掌握mikechen 本篇主要会详解以下六点&#xff1a; 1.AOP的定义 2.AOP的作用 3.AOP的应用场景 4.Spring AOP的术语 AOP核心概念Spring AOP 通知分类S…

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,如果执行…