Spring AOP使用之多切面运行顺序

目录
  • Spring AOP多切面运行顺序
    • 多切面运行顺序
    • AOP的应用场景
  • Spring AOP切面执行顺序和常见问题
    • 切面注解的执行顺序
    • 切面间的执行顺序
    • 常见问题示例

Spring AOP多切面运行顺序

多切面运行顺序

当一个方法的执行被多个切面共同切的时候,环绕通知只影响当前切面的通知顺序,例如创建两个切面logUtil,validateUtil两个切面共同监视计算器类的加法运算,add(int a,int b);测试中,看切面工具类的名称首字母,默认情况下a-z执行顺序,所以这个时候logUtil切面通知比validateUtil先执行通知;

所以顺序是:L的前置通知 -->v的前置通知–>执行add方法,然后v的后置通知–>V的后置返回–>L的后置通知–>L的后置返回。

但是当logUtil中加入了环绕通知,所以环绕通知要比logUtil的普通通知先执行,环绕通知功能很强大,在通过反射执行方法的前面我们可以更改这个方法的参数,但是普通通知不能这么做。虽然在logUtil加了环绕通知,但是这个环绕通知只是比logUtil的普通通知先执行无论是进入切面前还是出切面时,他并不影响validateUtil这个切面的普通通知的执行顺序,所以加了环绕通知执行顺序是

环绕前置–> log前置–>va前置–>目标方法–>va后置–>va返回–>环绕返回通知–>环绕后置–>log后置–>log返回。

图:

这里的validate切面就是图中的VaAspect;

对啦,可以更改默认的切面顺序,要在将要更改的切面类上加入@order(int value)注解,value默认值很大,超级大,越大执行的优先级越低,所以如果把它调成1就是先执行这个切面的通知。

AOP的应用场景

  • aop可以进行日志记录;
  • aop可以做权限验证
  • AOP可以做安全检查
  • AOP可以做事务控制

回忆基于注解的AOC配置

  • 将目标类和切面类都加入到IOC容器中。@Component
  • 告诉Spring哪个是切面类@Aspect
  • 在切面类中使用五个通知注解来配置切面中的这些方法都什么时候在那运行
  • 开启注解的aop功能。

不使用注解实现AOP配置。

1.切面类

public class LogUtil {
    public void performance(){}
    public void logStart(JoinPoint joinPoint)
    {
        //获取方法上的参数列表
        Object[] args = joinPoint.getArgs();
        //获取方法签名
        Signature signature = joinPoint.getSignature();
        String name = signature.getName();//获取方法名
        System.out.println("前置通知:"+name+" 方法开始执行了....参数是:"+ Arrays.asList(args) +"");
    }
    public void logReturn(JoinPoint point,Object result)
    {
        String name = point.getSignature().getName();
        Object[] args = point.getArgs();
        System.out.println("返回通知: "+name+"方法正常执行,返回结果是:"+result+"");
    }
    public void logException(JoinPoint point,Exception e)
    {
        String name = point.getSignature().getName();
        System.out.println("异常通知:"+name+" 方法出现了异常,异常是 "+e+"...");
    }
    public void logEnd(JoinPoint joinPoint)
    {
        String name = joinPoint.getSignature().getName();
        System.out.println("后置通知:"+name+"方法结束了");
    }
    //环绕通知
    public Object myAround(ProceedingJoinPoint proceedingJoinPoint){
        Object proceed = null;
            //获取方法名
        String name = proceedingJoinPoint.getSignature().getName();
            //获取执行方法的参数列表
        Object[] args = proceedingJoinPoint.getArgs();
        try {
            System.out.println("环绕前置通知:"+name+"方法开始执行了,参数是"+Arrays.asList(args)+"");
            //等于 method.invoke();通过反射执行指定方法
            proceed = proceedingJoinPoint.proceed();
            System.out.println("环绕返回通知:"+name+"方法返回结果是"+proceed+";");
        } catch (Throwable throwable) {
            System.out.println("环绕异常通知:异常是"+throwable+"");
            throwable.printStackTrace();
        }finally {
            System.out.println("环绕后置通知:"+name+"方法结束了");
        }
        return proceed;
    }

2.被切入的类(这里是一个计算器类)

package main.java.cn.zixue.domain;public class MyCalculator
{
  public int add(int a,int b)
 {
         return a+b;
 }
 public int sub(int a,int b)
 {
 	return a-b;
 }
     public int mul(int a,int b)
 {
 	return a*b;
  }
  public int dev(int a,int b)
  {
  		return a/b;
  }
  public double add(double a,float b,int c)
  {
          return a+b+c;
   }
 }

3.配置文件

<?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 http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <context:component-scan base-package="main.java.cn"></context:component-scan>
    <bean id="myCalculator" class="main.java.cn.zixue.domain.MyCalculator"></bean>
    <bean id="logUtil" class="main.java.cn.zixue.utils.LogUtil"></bean>
    <!--AOP名称空间-->
    <aop:config>
      <!--  制定切面的方法-->
        <aop:pointcut id="performance" expression="execution(public * main.java.cn.zixue.domain.MyCalculator.*(..))"></aop:pointcut>
        <!--指定切面-->
        <aop:aspect ref="logUtil">
            <aop:after method="logEnd" pointcut-ref="performance"></aop:after>
            <aop:before method="logStart" pointcut-ref="performance"></aop:before>
            <aop:after-returning method="logReturn" pointcut-ref="performance" returning="result"></aop:after-returning>
            <aop:after-throwing method="logException" pointcut-ref="performance" throwing="e"></aop:after-throwing>
            <aop:around method="myAround" pointcut-ref="performance"></aop:around>
        </aop:aspect>
    </aop:config>
</beans>

4.测试结果

@Test
public void Test02()
{
    MyCalculator myCalculator = (MyCalculator) context.getBean("myCalculator");
    myCalculator.add(1,10);
    System.out.println("========================");
}

前置通知:add 方法开始执行了…参数是:[1, 10]
环绕前置通知:add方法开始执行了,参数是[1, 10]
环绕返回通知:add方法返回结果是11;
环绕后置通知:add方法结束了
返回通知: add方法正常执行,返回结果是:11
后置通知:add方法结束了
====================**

普通前置通知->环绕通知->环绕返回->环绕后置->普通返回->普通后置

注解和配置文件在什么时候使用?该如何选择?

注解的优点:配置快速简洁。

配置文件的优点:功能丰富,注解有的他都可以实现,注解没有的他也有。

当遇到重要的切面时,用配置文件写,例如权限验证及管理。对于常用的普通的切面就用注解。

Spring AOP切面执行顺序和常见问题

切面注解的执行顺序

public Object aop(Method method,Object object) {
    try {
        try {
            /*doAround start*/
            doBefore();
            method.invoke(object);
            /*doAround end*/
        } finally {
            doAfter();
        }
        doAfterReturning();
    } catch (Exception e) {
        doAfterThrowing();
    }
}

切面间的执行顺序

切面之间使用older注解,区分调用顺序,Order值越小,那么切面越先执行(越后结束).

不指定Order,那么Order是默认值->Integer.MAX_VALUE. 如果Order相同,则是按照切面字母的顺序来执行切面.比如@Transactional和@Cacheable->对应的切面是TransactionInterceptor和CacheInterceptor,则先执行@Cacheable的切面

@Transactional也是通过切面实现,Order值是Integer.MAX_VALUE。(如果在service方法上同时添加带older的日志注解,在日志切面after里面报错,不会回滚事务)

常见问题示例

1.方法A调用同类中的方法B,方法B上的切面不会生效

问题示例:

@Component
public class StrategyService extends BaseStrategyService  {
    public PricingResponse getFactor(Map<String, String> pricingParams) {
        // 做一些参数校验,以及异常捕获相关的事情
        return this.loadFactor(tieredPricingParams);
    }

    @Override
    @StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2)
    private PricingResponse loadFactor(Map<String, String> pricingParams) {
        //代码执行
    }
}
 

原因:Spring的AOP是通过代理对象调用,只有这种调用方式,才能够在真正的对象的执行前后,能够让代理对象也执行相关代码,才能起到切面的作用。而对于上面使用this的方式调用,这种只是自调用,并不会使用代理对象进行调用,也就无法执行切面类。

 public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }

解决方法:使用AopContext.currentProxy()获取到代理对象,然后通过代理对象调用对应的方法。还有个地方需要注意,以上方式还需要将Aspect的expose-proxy设置成true。

@Component
public class StrategyService{
    public PricingResponse getFactor(Map<String, String> pricingParams) {
        // 做一些参数校验,以及异常捕获相关的事情
        // 这里不使用this.loadFactor而是使用AopContext.currentProxy()调用,目的是解决AOP代理不支持方法自调用的问题
        if (AopContext.currentProxy() instanceof StrategyService) {
            return ((StrategyService)AopContext.currentProxy()).loadFactor(tieredPricingParams);
        } else {

            // 部分实现没有被代理过,则直接进行自调用即可
            return loadFactor(tieredPricingParams);
        }
    }

    @Override
    @StrategyCache(keyName = "key0001", expireTime = 60 * 60 * 2)
    private PricingResponse loadFactor(Map<String, String> oricingParams) {
        //代码执行
    }
}

//还有个地方需要注意,以上方式还需要将Aspect的expose-proxy设置成true。如果是配置文件修改:
 <aop:aspectj-autoproxy proxy-target-class="true" expose-proxy="true"/>
//如果是SpringBoot,则修改应用启动入口类的注解:
@EnableAspectJAutoProxy(exposeProxy = true)
public class Application {
}
 

以上为个人经验,希望能给大家一个参考,也希望大家多多支持我们。

(0)

相关推荐

  • 深入理解Spring Aop的执行顺序

    首先回忆一下 AOP 的常用注解 @Before:前置通知:目标方法之前执行 @After:后置通知:目标方法之后执行 @AfterReturning:返回后通知:执行方法结束前执行 @AfterThrowing:异常通知:出现异常时执行 @Around:环绕通知:环绕目标方法执行 Spring4 中aop正常顺序 + 异常顺序 try{ @Before method.invoke(obj, args); @AfterReturning }catch(){ @AfterThrowing }fin

  • 聊聊Spring AOP @Before @Around @After等advice的执行顺序

    用过spring框架进行开发的人,多多少少会使用过它的AOP功能,都知道有@Before.@Around和@After等advice. 最近,为了实现项目中的输出日志和权限控制这两个需求,我也使用到了AOP功能. 我使用到了@Before.@Around这两个advice.但在,使用过程中,却对它们的执行顺序并不清楚. 为了弄清楚在不同情况下,这些advice到底是以怎么样的一个顺序进行执行的,我作了个测试,在此将其记录下来,以供以后查看. 前提 对于AOP相关类(aspect.pointcut

  • Spring AOP执行先后顺序实例详解

    这篇文章主要介绍了Spring AOP执行先后顺序实例详解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下 众所周知,spring声明式事务是基于AOP实现的,那么,如果我们在同一个方法自定义多个AOP,我们如何指定他们的执行顺序呢? 网上很多答案都是指定order,order越小越是最先执行,这种也不能算是错,但有些片面. 配置AOP执行顺序的三种方式: 通过实现org.springframework.core.Ordered接口 @Compo

  • Spring注解配置AOP导致通知执行顺序紊乱解决方案

    今天在测试Spring的AOP时,发现使用注解配置AOP的方式会导致通知的执行顺序紊乱.[最终通知居然在异常通知之前执行了] 测试代码 (1)定义TargetInterface目标接口 public interface TargetInterface { public abstract void targetProxy(); } (2)定义TargetImpl目标类 @Component("target") public class TargetImpl implements Targ

  • Spring Aop常见注解与执行顺序详解

    目录 Spring Aop 的常用注解 常见问题 示例代码 配置文件 接口类 实现类 aop 拦截器 测试类 执行结论 多切面的情况 代理失效场景 总结 Spring 一开始最强大的就是 IOC / AOP 两大核心功能,我们今天一起来学习一下 Spring AOP 常见注解和执行顺序. Spring Aop 的常用注解 首先我们一起来回顾一下 Spring Aop 中常用的几个注解: @Before 前置通知:目标方法之前执行 @After 后置通知:目标方法之后执行(始终执行) @After

  • Spring AOP使用之多切面运行顺序

    目录 Spring AOP多切面运行顺序 多切面运行顺序 AOP的应用场景 Spring AOP切面执行顺序和常见问题 切面注解的执行顺序 切面间的执行顺序 常见问题示例 Spring AOP多切面运行顺序 多切面运行顺序 当一个方法的执行被多个切面共同切的时候,环绕通知只影响当前切面的通知顺序,例如创建两个切面logUtil,validateUtil两个切面共同监视计算器类的加法运算,add(int a,int b):测试中,看切面工具类的名称首字母,默认情况下a-z执行顺序,所以这个时候lo

  • 深入理解Spring AOP

    一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就来学习一下. AOP AOP(Aspect Oriented Programming),即面向切面编程,可以说是OOP(Object Oriented Programming,面向对象编程)的补充和完善.OOP引入封装.继承.多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合.不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日

  • Spring AOP 基于注解详解及实例代码

    Spring AOP  基于注解详解及实例代码 1.启用spring对@AspectJ注解的支持: <beans xmlns:aop="http://www.springframework.org/schema/aop"...> <!--启动支持--> <aop:aspectj-autoproxy /> </beans> 也可以配置AnnotationAwareAspectJAutoProxyCreator Bean来启动Spring对@

  • Spring AOP  基于注解详解及实例代码

    Spring AOP  基于注解详解及实例代码 1.启用spring对@AspectJ注解的支持: <beans xmlns:aop="http://www.springframework.org/schema/aop"...> <!--启动支持--> <aop:aspectj-autoproxy /> </beans> 也可以配置AnnotationAwareAspectJAutoProxyCreator Bean来启动Spring对@

  • 谈谈Spring AOP中@Aspect的高级用法示例

    前言 本文主要跟大家分享介绍了关于Spring AOP中@Aspect的高级用法,下面话不多说了,来随着小编一起看看详细的介绍吧. 1 切点复合运算 支持在切点定义中加入以下运算符进行复合运算: 运算符 说明 && 与运算. ! 非运算. || 或运算. 2 切点命名 一般情况下,切点是直接声明在需要增强方法处,这种切点的声明方式称为匿名切点,匿名切点只能在声明处被使用 . 如果希望在其它地方可以重用这个切点,我们可以通过 @Pointcut 注解及切面类方法来命名它. public cl

  • spring aop之链式调用的实现

    概述 AOP(Aspect Orient Programming),我们一般称为面向方面(切面)编程,作为面向对象的一种补充,用于处理系统中分布于各个模块的横切关注点,比如事务管理.日志.缓存等等. Spring AOP采用的是动态代理,在运行期间对业务方法进行增强,所以不会生成新类,Spring AOP提供了对JDK动态代理的支持以及CGLib的支持.本章我们不关注aop代理类的实现,我简单实现一个指定次序的链式调用. 实现链式调用的 MethodInterceptor定义拦截器链,Metho

随机推荐