草庐IT

Spring AOP中增强Advice的执行顺序

一起来学习啊!!! 2023-04-19 原文

Spring AOP中增强Advice的执行顺序

本文主要验证Spring AOP中Advice的执行顺序问题。(Spring版本: 5.3.23)

Spring AOP中Advice分类

Spring AOP中Advice可分为如下五类:

  1. @Around
  2. @Before
  3. @AfterReturning
  4. @AfterThrowing
  5. @After

Advice相关概念参考

同一Apsect中不同类型Advice执行顺序

配置基础环境

  1. 依赖版本
  • Spring 版本为: 5.3.23
  • Spring Boot 版本为: 2.6.12
  • aspectjweaver 版本: 1.9.9.1
  1. 定义Spring Boot启动类
package sakura.springinaction;

@SpringBootApplication
@EnableAspectJAutoProxy
public class MySpringApplication {
	public static void main(String[] args) {
		SpringApplication.run(MySpringApplication.class, args);
	}
}
  1. 定义一个用于测试的Controller类
package sakura.springinaction.controller;

@Controller
@Slf4j
public class IndexController {
	@GetMapping("/time")
	@ResponseBody
	public String time() {
		LocalDateTime now = LocalDateTime.now();
		String nowTime = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
		log.info("Current time: " + nowTime);
		return nowTime;
	}
}
  1. 定义一个声明式切面 Apsect1
@Slf4j
@Component
@Aspect
public class Aspect1 {

   // 定义 Point Cut 切面
   @Pointcut("execution(public * sakura.springinaction.controller.*.*(..))")
   public void controllerLayer() {
   }

   // 定义Advice

   @Before("controllerLayer()")
   private void beforeAdvice2() {
   	log.info("Aspect_1 # @Before");
   }

   @After("controllerLayer() && @annotation(getMapping)")
   private void afterAdvice1(GetMapping getMapping) {
   	log.info("Aspect_1 # @afterAdvice" + " path: " + Arrays.toString(getMapping.value()));
   }

   @AfterReturning(pointcut = "controllerLayer()", returning = "val")
   private void afterReturningAdvice(Object val) {
   	log.info("Aspect_1 # @AfterReturning" + " returnValue: " + val);
   }

   @AfterThrowing(pointcut = "controllerLayer()", throwing = "thrower")
   private void afterThrowingAdvice(Throwable thrower) {
   	log.info("Aspect_1 # @AfterThrowing" + " thrower: " + thrower.getClass().getName());
   }

   @Around("controllerLayer() && @annotation(getMapping)")
   private Object aroundAdvice(ProceedingJoinPoint pjp, GetMapping getMapping) throws Throwable {
   	// Around 前置处理
   	Stopwatch stopwatch = Stopwatch.createStarted();
   	log.info("Aspect_1 # @Around-Before" + " methodName: " + pjp.getSignature().getName() + ", path: " + Arrays.toString(getMapping.value()));
   	Object result = pjp.proceed();
   	// Around 后置处理
   	log.info("Aspect_1 # @Around-After" + " methodName: " + pjp.getSignature().getName() + ", runTime: " + stopwatch.elapsed(TimeUnit.NANOSECONDS));
   	return result;
   }

}

实验结果

在 发起请求(http://localhost:8080/time) 后,日志输出如图:

结论

在同一个切面(Apsect)定义中对于同一个Join Point而言,不同类型的Advice执行先后顺序依次是:

  1. @Around 前置处理
  2. @Before
  3. @AfterReturning/@AfterThrowing
  4. @After
  5. @Around 后置置处理

优先级说明:

  • 对于进入Join PointAdvice而言(比如: @Around 前置处理,@Before),优先级越高,越先执行;
  • 对于从Join Point出来的Advice而言(比如: @Around 后置处理,@After),优先级越高,越后执行;
  • 优先级从高到低依次为: @Around, @Before,@After,@AfterReturning,@AfterThrowing

PS:

如果在同一个切面(Apsect)中定义了两个同类型的Advice(比如定义两个@Before), 对于某个Join Point而言这两个Advice都匹配,那么这两个Advice执行的先后顺序是无法确定的。

不同AspectAdvice执行顺序

问: 当不同的Aspect中的Advice 都匹配到了同一个Join Point,那么那个Aspect中的Advice 先执行,那个后执行呢?

答: 不确定 ,但是可以通过在class上添加注解@Order指定优先级确定执行顺序(参考文档)

实验一: Aspect1为高优先级,Aspect2为低优先级

  1. Aspect1 类似,再定义一个切面类Aspect2,如下
package sakura.springinaction.advice;
import org.springframework.core.annotation.Order;

@Slf4j
@Component
@Aspect
@Order(2)
public class Aspect2 {
  // 定义Advice
  @Before("sakura.springinaction.advice.Aspect1.controllerLayer()")
  private void beforeAdvice2() {
    log.info("Aspect_2 # @Before");
  }

  @After("sakura.springinaction.advice.Aspect1.controllerLayer() && @annotation(getMapping)")
  private void afterAdvice1(GetMapping getMapping) {
    log.info("Aspect_2 # @afterAdvice" + " path: " + Arrays.toString(getMapping.value()));
  }

  @AfterReturning(pointcut = "sakura.springinaction.advice.Aspect1.controllerLayer()", returning = "val")
  private void afterReturningAdvice(Object val) {
    log.info("Aspect_2 # @AfterReturning" + " returnValue: " + val);
  }

  @AfterThrowing(pointcut = "sakura.springinaction.advice.Aspect1.controllerLayer()", throwing = "thrower")
  private void afterThrowingAdvice(Throwable thrower) {
    log.info("Aspect_2 # @AfterThrowing" + " thrower: " + thrower.getClass().getName());
  }

  @Around("sakura.springinaction.advice.Aspect1.controllerLayer() && @annotation(getMapping)")
  private Object aroundAdvice(ProceedingJoinPoint pjp, GetMapping getMapping) throws Throwable {
    Stopwatch stopwatch = Stopwatch.createStarted();
    log.info("Aspect_2 # @Around-Before" + " methodName: " + pjp.getSignature().getName() + ", path: " + Arrays.toString(getMapping.value()));
    Object result = pjp.proceed();
    log.info("Aspect_2 # @Around-After" + " methodName: " + pjp.getSignature().getName() + ", runTime: " + stopwatch.elapsed(TimeUnit.NANOSECONDS));
    return result;
  }

}
  1. Aspect1 添加@Order注解指定优先级,如下
@Slf4j
@Component
@Aspect
@Order(1)
public class Aspect1 {
  //...
}

此时,Aspect1的优先级比Aspect2的优先级高。

实验结果

实验结果如下:

说明:

高优先级的Aspect1中的@Around前置处理和@Before先于低优先级的Aspect2执行,而@AfterReturning,@After@Around后置处理,则低优先级的Aspect2先执行。

实验二: Aspect1为低优先级,Aspect2为高优先级

  1. 更改两个Aspect@Order注解优先级,如下:
@Slf4j
@Component
@Aspect
@Order(2)
public class Aspect1 {
  //...
}
@Slf4j
@Component
@Aspect
@Order(1)
public class Aspect2 {
	//...
}

实验结果

实验结果如下:

结论

  1. 当不同的Aspect中的Advice 都匹配到了同一个Join Point,不同Aspect中的Advice 执行顺序不确定。
  2. 通过在Aspect类上添加注解@Order指定优先级,确定执行顺序,执行顺序满足如下规律
    1. 对于@Around前置处理 和@Before两种Advice而言,所在的Aspect优先级越高,越先执行
    2. 对于@AfterReturning@AfterThrowing,@After@Around后置处理 类型的Advice而言,所在的Aspect优先级越高,越后执行

参考资料:

  1. Aspect Oriented Programming with Spring
  2. AspectJ Programming Guide

本文主要目的是记录学习过程,加深对知识点理解; 如有行文有误,望指正。

有关Spring AOP中增强Advice的执行顺序的更多相关文章

  1. ruby-openid:执行发现时未设置@socket - 2

    我在使用omniauth/openid时遇到了一些麻烦。在尝试进行身份验证时,我在日志中发现了这一点:OpenID::FetchingError:Errorfetchinghttps://www.google.com/accounts/o8/.well-known/host-meta?hd=profiles.google.com%2Fmy_username:undefinedmethod`io'fornil:NilClass重要的是undefinedmethodio'fornil:NilClass来自openid/fetchers.rb,在下面的代码片段中:moduleNetclass

  2. ruby - Chef 执行非顺序配方 - 2

    我遵循了教程http://gettingstartedwithchef.com/,第1章。我的运行list是"run_list":["recipe[apt]","recipe[phpap]"]我的phpapRecipe默认Recipeinclude_recipe"apache2"include_recipe"build-essential"include_recipe"openssl"include_recipe"mysql::client"include_recipe"mysql::server"include_recipe"php"include_recipe"php::modul

  3. ruby - 为什么 Ruby 的 each 迭代器先执行? - 2

    我在用Ruby执行简单任务时遇到了一件奇怪的事情。我只想用每个方法迭代字母表,但迭代在执行中先进行:alfawit=("a".."z")puts"That'sanalphabet:\n\n#{alfawit.each{|litera|putslitera}}"这段代码的结果是:(缩写)abc⋮xyzThat'sanalphabet:a..z知道为什么它会这样工作或者我做错了什么吗?提前致谢。 最佳答案 因为您的each调用被插入到在固定字符串之前执行的字符串文字中。此外,each返回一个Enumerable,实际上您甚至打印它。试试

  4. ruby - 检查是否通过 require 执行或导入了 Ruby 程序 - 2

    如何检查Ruby文件是否是通过“require”或“load”导入的,而不是简单地从命令行执行的?例如:foo.rb的内容:puts"Hello"bar.rb的内容require'foo'输出:$./foo.rbHello$./bar.rbHello基本上,我想调用bar.rb以不执行puts调用。 最佳答案 将foo.rb改为:if__FILE__==$0puts"Hello"end检查__FILE__-当前ruby​​文件的名称-与$0-正在运行的脚本的名称。 关于ruby-检查是否

  5. postman——集合——执行集合——测试脚本——pm对象简单示例02 - 2

    //1.验证返回状态码是否是200pm.test("Statuscodeis200",function(){pm.response.to.have.status(200);});//2.验证返回body内是否含有某个值pm.test("Bodymatchesstring",function(){pm.expect(pm.response.text()).to.include("string_you_want_to_search");});//3.验证某个返回值是否是100pm.test("Yourtestname",function(){varjsonData=pm.response.json

  6. ruby-on-rails - rbenv:从 RVM 移动到 rbenv 后,在 Jenkins 执行 shell 中找不到命令 - 2

    我从Ubuntu服务器上的RVM转移到rbenv。当我使用RVM时,使用bundle没有问题。转移到rbenv后,我在Jenkins的执行shell中收到“找不到命令”错误。我内爆并删除了RVM,并从~/.bashrc'中删除了所有与RVM相关的行。使用后我仍然收到此错误:rvmimploderm~/.rvm-rfrm~/.rvmrcgeminstallbundlerecho'exportPATH="$HOME/.rbenv/bin:$PATH"'>>~/.bashrcecho'eval"$(rbenvinit-)"'>>~/.bashrc.~/.bashrcrbenvversions

  7. ruby - 如何使用 Selenium Webdriver 根据 div 的内容执行操作? - 2

    我有一个使用SeleniumWebdriver和Nokogiri的Ruby应用程序。我想选择一个类,然后对于那个类对应的每个div,我想根据div的内容执行一个Action。例如,我正在解析以下页面:https://www.google.com/webhp?sourceid=chrome-instant&ion=1&espv=2&ie=UTF-8#q=puppies这是一个搜索结果页面,我正在寻找描述中包含“Adoption”一词的第一个结果。因此机器人应该寻找带有className:"result"的div,对于每个检查它的.descriptiondiv是否包含单词“adoption

  8. ruby-on-rails - Rake 任务仅调用一次时执行两次 - 2

    我写了一个非常简单的rake任务来尝试找到这个问题的根源。namespace:foodotaskbar::environmentdoputs'RUNNING'endend当在控制台中执行rakefoo:bar时,输出为:RUNNINGRUNNING当我执行任何rake任务时会发生这种情况。有没有人遇到过这样的事情?编辑上面的rake任务就是写在那个.rake文件中的所有内容。这是当前正在使用的Rakefile。requireFile.expand_path('../config/application',__FILE__)OurApp::Application.load_tasks这里

  9. ruby-on-rails - 在 RSpec 中,如何以任意顺序期望具有不同参数的多条消息? - 2

    RSpec似乎按顺序匹配方法接收的消息。我不确定如何使以下代码工作:allow(a).toreceive(:f)expect(a).toreceive(:f).with(2)a.f(1)a.f(2)a.f(3)我问的原因是a.f的一些调用是由我的代码的上层控制的,所以我不能对这些方法调用添加期望。 最佳答案 RSpecspy是测试这种情况的一种方式。要监视一个方法,用allowstub,除了方法名称之外没有任何约束,调用该方法,然后expect确切的方法调用。例如:allow(a).toreceive(:f)a.f(2)a.f(1)

  10. ruby-on-rails - 只有当不是 nil 时才执行映射? - 2

    如果names为nil,则以下中断。我怎样才能让这个map只有在它不是nil时才执行?self.topics=names.split(",").mapdo|n|Topic.where(name:n.strip).first_or_create!end 最佳答案 其他几个选项:选项1(在其上执行map时检查split的结果):names_list=names.try(:split,",")self.topics=names_list.mapdo|n|Topic.where(name:n.strip).first_or_create!e

随机推荐