11.1、环境搭建

创建名为spring_aop_annotation的新module,过程参考9.1节

11.1.1、配置打包方式和依赖

注意:AOP需要在IOC的基础上实现,因此需要导入IOC的依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion> <groupId>org.rain</groupId>
<artifactId>spring_aop_annotation</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>jar</packaging> <dependencies>
<!-- Spring-IOC的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.1</version>
</dependency> <!-- spring-AOP的依赖 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.1</version>
</dependency> <!-- junit测试 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies> </project>

11.1.2、创建Calculator接口及实现类

package org.rain.spring.aop.annotation;

/**
* @author liaojy
* @date 2023/8/12 - 17:43
*/
public interface Calculator { int add(int i, int j);
int sub(int i, int j);
int mul(int i, int j);
int div(int i, int j); }

package org.rain.spring.aop.annotation;

import org.springframework.stereotype.Component;

/**
* @author liaojy
* @date 2023/8/12 - 17:45
*/ // @Component注解保证这个目标类能够放入IOC容器
@Component
public class CalculatorImpl implements Calculator {
public int add(int i, int j) { int result = i + j;
System.out.println("方法内部 result = " + result);
return result; } public int sub(int i, int j) { int result = i - j;
System.out.println("方法内部 result = " + result);
return result; } public int mul(int i, int j) { int result = i * j;
System.out.println("方法内部 result = " + result);
return result; } public int div(int i, int j) { int result = i / j;
System.out.println("方法内部 result = " + result);
return result; }
}

11.1.3、创建切面类LoggerAspect

package org.rain.spring.aop.annotation;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component; /**
* @author liaojy
* @date 2023/8/12 - 17:56
*/ // @Aspect表示这个类是一个切面类
@Aspect
// @Component注解保证这个切面类能够放入IOC容器
@Component
public class LoggerAspect {
}

11.1.4、创建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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd"> <!--
对指定的package进行扫描,将使用组件注解的类的对象(本示例是目标对象和切面对象),交给spring的ioc容器来管理
-->
<context:component-scan base-package="org.rain.spring.aop.annotation"></context:component-scan> <!--
开启基于注解的AOP功能,该功能会为目标对象自动生成代理
-->
<aop:aspectj-autoproxy></aop:aspectj-autoproxy> </beans>

11.2、前置通知的使用

11.2.1、基本示例

11.2.1.1、配置前置方法

    /*
* @Before注解:用于将方法标识为前置通知(方法)
* @Before注解的value属性值为切入点表达式,其作用是将该前置通知(方法)安插到对应目标方法的连接点上
* */
@Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
public void beforeMethod(){
System.out.println("LoggerAspect,前置通知");
}

11.2.1.2、测试使用效果

由控制台日志可知,切面类的前置通知(方法),通过切入点表达式,作用到了目标方法的连接点上

    @Test
public void testAOPByAnnotation(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml"); // 注意:这里不能直接获取目标对象来使用;因为使用了AOP之后,IOC容器中就只有对应目标对象的代理对象;
// 如果强行获取目标对象,则报错:NoSuchBeanDefinitionException
//Calculator calculator = ioc.getBean(CalculatorImpl.class); // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class); // 只能通过代理对象来访问目标对象中的方法
calculator.add(1,2); }

11.2.2、高级示例

11.2.2.1、改进前置方法

该示例中(前置)通知方法引入了连接点参数,通过连接点参数,可以动态获取(切入点表达式)对应的目标方法的名称和参数列表

    /*
* @Before注解:用于将方法标识为前置通知(方法)
* @Before注解的value属性值为切入点表达式,其作用是将该前置通知(方法)安插到对应目标方法的连接点上
* */
@Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")
// joinPoint参数:可以获取(通过切入点表达式定位出的)连接点的相关信息
public void beforeMethod(JoinPoint joinPoint){
// 获取连接点所对应目标方法的名称
String methodName = joinPoint.getSignature().getName();
// 获取连接点所对应目标方法的参数列表
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}

11.2.2.2、测试使用效果

11.3、切入点表达式的进阶用法

11.3.1、高频用法示例

    // @Before("execution(public int org.rain.spring.aop.annotation.CalculatorImpl.add(int , int))")

    /**
* 第一个*表示任意访问修饰符和返回值类型,
* 第二个*表示该类的任意方法名称,
* (..)表示方法的任意参数列表
* 在类的位置也可以使用*,表示当前包下所有的类,
* 在包的位置也可以使用*,表示当前包下所有的子包,
*/
@Before("execution(* org.rain.spring.aop.annotation.CalculatorImpl.*(..))")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}

11.3.2、详细语法图解

11.3.3、复用切入点表达式

11.3.3.1、声明公共的切入点表达式

    @Pointcut("execution(* org.rain.spring.aop.annotation.CalculatorImpl.*(..))")
public void pointCutOne(){}

11.3.3.2、在同一个切面类中复用

    // @Before注解的value属性值,可以设置为使用了@Pointcut注解标识的方法名,从而复用该@Pointcut注解定义的切入点表达式
@Before("pointCutOne()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}

11.3.3.3、在不同一个切面类中复用

    // 复用其他切面类中@Pointcut注解定义的切入点表达式,
// @Before注解的value属性值,需要设置为使用了@Pointcut注解标识的(全限定类名+)方法名
@Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
public void beforeMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}

11.4、其他通知的使用

11.4.1、后置通知

11.4.1.1、配置后置方法

    // @After注解:用于将方法标识为后置通知(方法)
@After("pointCutOne()")
public void afterMethod(JoinPoint joinPoint){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->后置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}

11.4.1.2、测试使用效果

由控制台日志可知,后置通知在目标对象方法的finally子句中执行(一般用于释放资源)

    @Test
public void testAOPByAnnotation(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml"); // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class); // 只能通过代理对象来访问目标对象中的方法
calculator.div(1,0); }

11.4.2、返回通知

11.4.2.1、配置返回通知

    /**
* @AfterReturning注解:用于将方法标识为返回通知(方法)
* returning属性:指定(返回)通知方法中的某个参数(名),用于接收目标对象方法的返回值
*/
@AfterReturning(value = "pointCutOne()",returning = "result")
public void afterReturningMethod(JoinPoint joinPoint,Object result){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->返回通知,方法名:"+methodName+",结果:"+ result);
}

11.4.2.2、测试使用效果

由控制台日志可知,返回通知在目标对象方法的返回值之后执行

    @Test
public void testAOPByAnnotation(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml"); // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class); // 只能通过代理对象来访问目标对象中的方法
calculator.div(1,1); }

11.4.3、异常通知

11.4.3.1、配置异常通知

    /**
* @AfterThrowing注解:用于将方法标识为异常通知(方法)
* throwing属性:指定(异常)通知方法中的某个参数(名),用于接收目标对象方法出现的异常
*/
@AfterThrowing(value = "pointCutOne()",throwing = "ex")
public void afterThrowingMethod(JoinPoint joinPoint,Exception ex){
String methodName = joinPoint.getSignature().getName();
Object[] args = joinPoint.getArgs();
System.out.println("LoggerAspect-->异常通知,方法名:"+methodName+",异常:"+ ex);
}

11.4.3.2、测试使用效果

由控制台日志可知,异常通知在目标对象方法的catch子句中执行

    @Test
public void testAOPByAnnotation(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml"); // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class); // 只能通过代理对象来访问目标对象中的方法
calculator.div(1,0); }

11.4.4、通知的执行顺序

11.4.4.1、Spring版本5.3.x以前

  • 前置通知

  • 目标操作

  • 后置通知

  • 返回通知或异常通知

11.4.4.2、Spring版本5.3.x以后

本示例

  • 前置通知

  • 目标操作

  • 返回通知或异常通知

  • 后置通知

11.5、环绕通知

11.5.1、配置环绕通知

环绕通知和动态代理的形式,非常相似

    /**
* @Around注解:用于将方法标识为环绕通知(方法)
* 环绕通知(方法)使用的参数是ProceedingJoinPoint类型
* 环绕通知(方法)的返回值,必须和目标对象方法的返回值一致
*/
@Around("pointCutOne()")
public Object aroundMethod(ProceedingJoinPoint proceedingJoinPoint){
String methodName = proceedingJoinPoint.getSignature().getName();
Object[] args = proceedingJoinPoint.getArgs();
Object result = null;
try {
System.out.println("LoggerAspect-->环绕前置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
// 表示目标对象方法的执行
result = proceedingJoinPoint.proceed();
System.out.println("LoggerAspect-->环绕返回通知,方法名:"+methodName+",结果:"+ result);
} catch (Throwable throwable) {
throwable.printStackTrace();
System.out.println("LoggerAspect-->环绕异常通知,方法名:"+methodName+",异常:"+ throwable);
}finally {
System.out.println("LoggerAspect-->环绕后置通知,方法名:"+methodName+",参数:"+ Arrays.toString(args));
}
return result;
}

11.5.2、测试使用效果

注意:因为环绕通知包括了其他四种通知,所以一般要么配置其他四种通知,要么只配置环绕通知;本示例为了展示效果才同时配置

    @Test
public void testAOPByAnnotation(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml"); // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class); // 只能通过代理对象来访问目标对象中的方法
calculator.div(1,1); }

11.6、切面的优先级

11.6.1、创建其他切面类ValidateAspect

package org.rain.spring.aop.annotation;

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component; /**
* @author liaojy
* @date 2023/8/15 - 7:49
*/
@Aspect
@Component
public class ValidateAspect {
}

11.6.2、配置前置通知方法

    @Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
public void beforeMethod(){
System.out.println("ValidateAspect-->前置通知");
}

11.6.3、测试使用效果

由控制台日志可知,ValidateAspect切面的前置通知方法生效了,但执行顺序在LoggerAspect切面的前置通知方法的后面

    @Test
public void testAOPByAnnotation(){ ApplicationContext ioc = new ClassPathXmlApplicationContext("spring-aop-annotation.xml"); // 虽然不知道代理对象的类名,但可以通过代理对象和目标对象共同实现的接口类型来从ioc容器中获取代理对象
Calculator calculator = ioc.getBean(Calculator.class); // 只能通过代理对象来访问目标对象中的方法
calculator.div(1,1); }

11.6.4、调整切面的优先级

package org.rain.spring.aop.annotation;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component; /**
* @author liaojy
* @date 2023/8/15 - 7:49
*/
@Aspect
@Component
// @Order注解:用于设置切面的优先级,value属性值越小,优先级越高,默认值为Integer的最大值
@Order(2023)
public class ValidateAspect { @Before("org.rain.spring.aop.annotation.LoggerAspect.pointCutOne()")
public void beforeMethod(){
System.out.println("ValidateAspect-->前置通知");
} }

11.6.5、测试调整后的效果

由控制台日志可知,ValidateAspect切面的前置通知方法的执行顺序,在LoggerAspect切面的前置通知方法的前面

这是因为ValidateAspect切面的@Order注解的value属性值已设为2023,要小于LoggerAspect切面所使用的默认值(Integer的最大值2147483647)

11.7、扩展知识

  • AspectJ本质上是静态代理,将代理逻辑“织入”被代理的目标类编译得到的字节码文件,但最终效果是动态的。

  • weaver就是织入器,Spring只是借用了AspectJ中的注解。

11、Spring之基于注解的AOP的更多相关文章

  1. spring中基于注解使用AOP

    本文内容:spring中如何使用注解实现面向切面编程,以及如何使用自定义注解. 一个场景 比如用户登录,每个请求发起之前都会判断用户是否登录,如果每个请求都去判断一次,那就重复地做了很多事情,只要是有 ...

  2. Spring AspectJ基于注解的AOP实现

    对于AOP这种编程思想,很多框架都进行了实现.Spring就是其中之一,可以完成面向切面编程.然而,AspectJ也实现了AOP的功能,且实现方式更为简捷,使用更加方便,而且还支持注解式开发.所以,S ...

  3. Spring:基于注解的Spring MVC

    什么是Spring MVC Spring MVC框架是一个MVC框架,通过实现Model-View-Controller模式来很好地将数据.业务与展现进行分离.从这样一个角度来说,Spring MVC ...

  4. Spring_Spring与AOP_AspectJ基于注解的AOP实现

    一.AspectJ.Spring与AOP的关系 AspectJ是一个面向切面的框架,它扩展了Java语言.AspectJ定义了AOP语法,所以它有一个专门的编译器用来生成遵守Java字节编码规范的Cl ...

  5. spring的基于xml的AOP配置案例和切入点表达式的一些写法

    <?xml version="1.0" encoding="UTF-8"?><beans xmlns="http://www.spr ...

  6. Spring boot 基于注解方式配置datasource

    Spring boot 基于注解方式配置datasource 编辑 ​ Xml配置 我们先来回顾下,使用xml配置数据源. 步骤: 先加载数据库相关配置文件; 配置数据源; 配置sqlSessionF ...

  7. Spring中基于xml的AOP

    1.Aop 全程是Aspect Oriented Programming 即面向切面编程,通过预编译方式和运行期动态代理实现程序功能的同一维护的一种技术.Aop是oop的延续,是软件开发中的 一个热点 ...

  8. Spring基础知识之基于注解的AOP

    背景概念: 1)横切关注点:散布在应用中多处的功能称为横切关注点 2)通知(Advice):切面完成的工作.通知定了了切面是什么及何时调用. 5中可以应用的通知: 前置通知(Before):在目标方法 ...

  9. 阶段3 2.Spring_08.面向切面编程 AOP_9 spring基于注解的AOP配置

    复制依赖和改jar包方式 src下的都复制过来. 复制到新项目里了 bean.xml里面复制上面一行代码到下面.把aop改成context. 配置spring容器创建时要扫描的包 Service的配置 ...

  10. Spring+Mybatis基于注解整合Redis

    基于这段时间折腾redis遇到了各种问题,想着整理一下.本文主要介绍基于Spring+Mybatis以注解的形式整合Redis.废话少说,进入正题. 首先准备Redis,我下的是Windows版,下载 ...

随机推荐

  1. 树莓派上使用docker部署aria2,minidlna

    目前在树莓派上安装aria2跟minidlna能搜到的教程基本上都是直接apt-get install安装的.现在是docker的时代了,其实这2个东西可以直接使用docker run跑起来.有什么问 ...

  2. 这10个Lambda表达式必须掌握,简化你的代码,提高生产力

    Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名 ...

  3. C#中BitConverter.ToUInt16、BitConverter.ToUInt32原理与用法详解

    一.基础知识 a.1字节=8位(1Byte=8bit)   二进制表示:11111111   十进制表示:255 计算机内部约定用多少字节来规范数值,比如红绿蓝三色在计算机中只分配了一个字节,一个字节 ...

  4. 【python基础】编写/运行hello world项目

    1.编写hello world项目 编程界每种语言的第一个程序往往都是输出hello world.因此我们来看看,如何用Python输出hello world. 1.如果你是初学者,main.py中的 ...

  5. 基于Quartz的可视化UI操作组件GZY.Quartz.MUI更新说明(附:在ABP中集成GZY.Quartz.MUI可视化操作组件)

    前言 时隔2年.(PS:其实陆陆续续在优化,不过没发博客).. .本组件又迎来了新的更新... 很久没更新博客了.生了娃,换了工作单位,太忙了..实在抱歉 NET Core 基于Quartz的UI可视 ...

  6. ODOO升级模块后到系统进入不了,报错500

    有时候安装后者升级odoo相关模块后会导致系统进入不了,报错500,此时我们可以通过Odoo命令行卸载相关模块 此方法适用于在安装或升级某个模块后导致崩库,进不去桌面的情况下使用.原理是通过odoo- ...

  7. 800名科学家Nature联名发文主张废除p值!

    每年,全球有数百万学生修读统计学课程.随着世界上的数据量越来越大,统计学已成为越来越受欢迎的话题.如果大多数学生都从这门课中记住一点,那可能就是"统计显著性"和"p 值& ...

  8. 移动App测试概述:移动App特性

    移动App测试概述:移动App特性 移动App在现代人的日常生活中扮演着越来越重要的角色,因而对于它们的质量和稳定性的要求也越来越高.为了确保App的质量,开发商需要进行充分的测试和检验.本文将讨论移 ...

  9. 一种实现Spring动态数据源切换的方法

    1 目标 不在现有查询代码逻辑上做任何改动,实现dao维度的数据源切换(即表维度) 2 使用场景 节约bdp的集群资源.接入新的宽表时,通常uat验证后就会停止集群释放资源,在对应的查询服务器uat环 ...

  10. 2023-06-19:讲一讲Redis分布式锁的实现?

    2023-06-19:讲一讲Redis分布式锁的实现? 答案2023-06-19: Redis分布式锁最简单的实现 要实现分布式锁,确实需要使用具备互斥性的Redis操作.其中一种常用的方式是使用SE ...