0


Spring之AOP切面编程

文章目录

一、代理模式

  • 二十三种设计模式中的一种,属于结构型模式
  • 它的作用就是通过提供一个代理类,让我们在调用目标方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用
  • 让不属于目标方法核心逻辑的代码从目标方法中剥离出来——解耦
  • 调用目标方法时先调用代理对象的方法,减少对目标方法的调用和打扰,同时让附加功能能够集中在一起也有利于统一维护
  • 之前做的设计模式系列这里排上用场了:设计模式(六):结构型之代理模式

二、AOP概念及相关术语

1、概述

  • AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程
  • 它是面向对象编程的一种补充和完善- 它以通过预编译方式和运行期动态代理方式实现- 在不修改源代码的情况下,给程序动态统一添加额外功能的一种技术

2、相关术语

2.1、横切关注点

  • 分散在每个各个模块中解决同一样的问题,如用户验证、日志管理、事务处理、数据缓存都属于横切关注点
  • 从每个方法中抽取出来的同一类非核心业务
  • 在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强
  • 这个概念不是语法层面的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点

在这里插入图片描述

2.2、通知(增强)

  • 增强,通俗说,就是你想要增强的功能,比如 安全,事务,日志等
  • 每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法

  • 前置通知:在被代理的目标方法执行
  • 返回通知:在被代理的目标方法成功结束后执行(代码异常则不执行)
  • 异常通知:在被代理的目标方法异常结束后执行
  • 后置通知:在被代理的目标方法最终结束后执行(肯定会执行,类似finally)
  • 环绕通知:使用try…catch…finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置

在这里插入图片描述

2.3、切面

封装通知方法的类

在这里插入图片描述

2.4、目标

被代理的目标对象

2.5、代理

向目标对象应用通知之后创建的代理对象

2.6、连接点

  • 这也是一个纯逻辑概念,不是语法定义的
  • 把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉点就是连接点
  • 通俗说,就是spring允许你使用通知的地方

在这里插入图片描述

2.7、切入点

  • 定位连接点的方式
  • 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)
  • 如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句
  • Spring 的 AOP 技术可以通过切入点定位到特定的连接点。通俗说,要实际去增强的方法
  • 切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条件

三、基于注解的AOP

1、切入点表达式语法

1.1、execute表达式(常用)

  • *号代替“权限修饰符”和“返回值”部分表示“权限修饰符”和“返回值”不限
  • 在包名的部分,一个*号只能代表包的层次结构中的一层,表示这一层是任意的- 例如:.Hello匹配com.Hello,不匹配com.xc.Hello*
  • 在包名的部分,使用“*..”表示包名任意、包的层次深度任意
  • 在类名的部分,类名部分整体用*号代替,表示类名任意
  • 在类名的部分,可以使用*号代替类名的一部分- 例如:*Service匹配所有名称以Service结尾的类或接口
  • 在方法名部分,可以使用*号表示方法名任意
  • 在方法名部分,可以使用*号代替方法名的一部分- 例如:*Operation匹配所有方法名以Operation结尾的方法
  • 在方法参数列表部分,使用(..)表示参数列表任意
  • 在方法参数列表部分,使用(int,..)表示参数列表以一个int类型的参数开头
  • 在方法参数列表部分,基本数据类型和对应的包装类型是不一样的- 切入点表达式中使用 int 和实际方法中 Integer 是不匹配的
  • 在方法返回值部分,如果想要明确指定一个返回值类型,那么必须同时写明权限修饰符- 例如:execution(public int *..*Service.*(.., int)) 正确- 例如:execution(* int *..*Service.*(.., int)) 错误

在这里插入图片描述

1.2、within表达式

  • 给定class的所有方法
  • 表达式格式:包名.* 或者 包名..* - within(com.xc.service.*):拦截包中任意方法,不包含子包中的方法- within(com.xc.service..*):拦截包或者子包中定义的方法
  • within与execution相比,粒度更大,仅能实现到接口级别
  • 而execution可以精确到方法的返回值,参数个数、修饰符、参数类型等

1.3、this表达式

  • 代理对象为指定的类型会被拦截
  • this(com.xc.service.AccountServiceImpl):AccountService接口、AccountServiceImpl实现类- 如果使用jdk动态代理生成的对象,不是AccountServiceImpl类型不会拦截- 如果使用cglib代理生成的是AccountServiceImpl的子类,会被拦截

1.4、target表达式

  • 目标对象为指定的类型会被拦截
  • target(com.xc.service.AccountService)
  • this作用于代理对象,target作用于目标对象

1.5、args 表达式

  • 方法参数为指定的类型会被拦截
  • args(com.xc.UserModel):匹配只有一个参数,且类型为UserModel
  • args(type1,type2,typeN):匹配多个参数
  • args(com.xc.UserModel,..):匹配任意多个参数

1.6、@target表达式

  • 目标对象中包含指定注解,调用该目标对象的任意方法都会被拦截
  • @target(com.xc.MyAnnotation)

1.7、@within表达式

  • @within(com.xc.MyAnnotation)
  • @target 和 @within 的不同点- 父类有注解,但子类没有注解的话,@within和@target是不会对子类生效的- 子类没有注解的情况下,只有没有被重写的有注解的父类的方法才能被@within匹配到- 如果父类无注解,子类有注解的话,@target对父类所有方法生效,@within只对重载过的方法生效

1.8、@annotation表达式

  • 方法上有指定注解会被拦截(注解作用在方法上面)
  • @annotation(com.xc.MyAnnotation)

1.9、@args表达式

  • 方法参数上有指定注解会被拦截
  • @args(com.xc.Annotation1):匹配1个参数,且第1个参数所属的类中有Annotation1注解
  • @args(com.xc.Annotation1,com.xc.Annotation2):匹配多个参数,且多个参数所属的类型上都有指定的注解
  • @args(com.xc.Annotation1,..):匹配多个参数,且第一个参数所属的类中有Annotation1注解

2、准备工作

添加依赖

<!--spring context依赖--><!--当你引入Spring Context依赖之后,表示将Spring的基础依赖引入了--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>6.0.2</version></dependency><!--spring aop依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aop</artifactId><version>6.0.2</version></dependency><!--spring aspects依赖--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>6.0.2</version></dependency>

准备被代理的目标资源(计算器接口-加减乘除)

接口:

publicinterfaceCalculator{intadd(int i,int j);intsub(int i,int j);intmul(int i,int j);intdiv(int i,int j);}

实现类:

@ComponentpublicclassCalculatorImplimplementsCalculator{@Overridepublicintadd(int i,int j){int result = i + j;System.out.println("方法内部 result = "+ result);return result;}@Overridepublicintsub(int i,int j){int result = i - j;System.out.println("方法内部 result = "+ result);return result;}@Overridepublicintmul(int i,int j){int result = i * j;System.out.println("方法内部 result = "+ result);return result;}@Overridepublicintdiv(int i,int j){int result = i / j;System.out.println("方法内部 result = "+ result);return result;}}

3、创建切面类并配置

@Aspect//切面类@Component//ioc容器publicclassLogAspect{//设置切入点和通知类型//切入点表达式: execution(访问修饰符 增强方法返回类型 增强方法所在类全路径.方法名称(方法参数))//通知类型:// 前置 @Before(value="切入点表达式配置切入点")//@Before(value = "execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))")@Before(value ="execution(public int com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))")publicvoidbeforeMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();System.out.println("前置通知,方法名称:"+ methodName +",参数:"+Arrays.toString(args));}// 后置 @After()//@After(value = "com.xc.spring6.aop.annoaop.LogAspect.pointCut()")@After(value ="pointCut()")publicvoidafterMethod(JoinPoint joinPoint){String methodName = joinPoint.getSignature().getName();System.out.println("后置通知,方法名称:"+ methodName);}// 返回 @AfterReturning@AfterReturning(value ="execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))", returning ="result")publicvoidafterReturningMethod(JoinPoint joinPoint,Object result){String methodName = joinPoint.getSignature().getName();System.out.println("返回通知,方法名称:"+ methodName +",返回结果:"+ result);}// 异常 @AfterThrowing 获取到目标方法异常信息//目标方法出现异常,这个通知执行@AfterThrowing(value ="execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))", throwing ="ex")publicvoidafterThrowingMethod(JoinPoint joinPoint,Throwable ex){String methodName = joinPoint.getSignature().getName();System.out.println("异常通知,方法名称:"+ methodName +",异常信息:"+ ex);}// 环绕 @Around()@Around("execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))")publicObjectaroundMethod(ProceedingJoinPoint joinPoint){// 方法名、参数名String methodName = joinPoint.getSignature().getName();Object[] args = joinPoint.getArgs();Object result =null;try{System.out.println("环绕通知 ==> 目标方法之前执行");//调用目标方法
            result = joinPoint.proceed();System.out.println("环绕通知 ==> 目标方法返回值之后");}catch(Throwable throwable){
            throwable.printStackTrace();System.out.println("环绕通知 ==> 目标方法出现异常执行");}finally{System.out.println("环绕通知 ==> 目标方法执行完毕执行");}return result;}//重用切入点表达式@Pointcut(value ="execution(* com.xc.spring6.aop.annoaop.CalculatorImpl.*(..))")publicvoidpointCut(){}}

配置类:

@Configuration@ComponentScan(basePackages ="com.xc")@EnableAspectJAutoProxy(proxyTargetClass =true)publicclassAopConfig{}

4、测试

publicclassClient{publicstaticvoidmain(String[] args){ApplicationContext context =newAnnotationConfigApplicationContext(AopConfig.class);Calculator calculator = context.getBean(Calculator.class);int result = calculator.add(2,3);System.out.println("执行结果:"+result);}}

输出结果:

环绕通知 ==> 目标方法之前执行
前置通知,方法名称:add,参数:[2,3]
方法内部 result =5
返回通知,方法名称:add,返回结果:5
后置通知,方法名称:add
环绕通知 ==> 目标方法返回值之后
环绕通知 ==> 目标方法执行完毕执行
执行结果:5

5、切面的优先级

  • 相同目标方法上同时存在多个切面时,切面的优先级控制切面的内外嵌套顺序 - 优先级高的切面:外面- 优先级低的切面:里面
  • 使用@Order注解可以控制切面的优先级- @Order(较小的数):优先级高- @Order(较大的数):优先级低

在这里插入图片描述

两个切面的执行顺序

在这里插入图片描述

6、 Spring5.2.7.RELEASE之前版本顺序

新版顺序:环绕通知(后)放在最后执行,后置通知(最终通知)在返回通知后面

环绕通知 ==> 目标方法之前执行
前置通知,方法名称:add,参数:[2,1]
方法内部 result =3
环绕通知 ==> 目标方法返回值之后
后置通知,方法名称:add
返回通知,方法名称:add,返回结果:3

四、@Aspectj番外篇

  • AspectJ:全称Eclipse AspectJ,可以单独使用,也可以整合到其它框架中
  • 单独使用AspectJ时需要使用专门的编译器ajc
  • spring基于@AspectJ注解的方式,也只是使用一些注解,并没有依赖于 AspectJ 实现具体的功能

Spring AOP与单独使用@AspectJ区别

在这里插入图片描述

aspectj的类加载期织入的实现方式

  • aspectj是AOP一种实现,主要原理是用asm做字节码替换来达到AOP的目的
  • aspectj有3个时间点可以做织入- 编译期(Compile-time weaving):把aspect类(aop的切面)和目标类(被aop的类)放在一起用ajc编译- 编译期后(Post-compile weaving):目标类可能已经被打成了一个jar包,这时候也可以用ajc命令将jar再织入一次- 类加载期(Load-time weaving):在jvm加载类的时候,做字节码替换

  • 其中前2个时间点,我们可以理解为静态织入,因为在class文件生成后,就已经织入好了
  • 类加载期织入,可以理解为“动态织入”(注意不同于java动态代理的“动态”),因为这个类替换是在jvm加载类的时候完成的

本文转载自: https://blog.csdn.net/qq_35512802/article/details/131402170
版权归原作者 冬天vs不冷 所有, 如有侵权,请联系我们删除。

“Spring之AOP切面编程”的评论:

还没有评论