Spring AOP 中的环绕通知 – @Around

Spring AOP 中的环绕通知 – @Around

简介

在本文中,您将了解 Spring AOP 中围绕匹配的 Joinpoint 执行运行的环绕建议。使用 @Around 注解声明环绕通知。

您已经了解了上一篇文章中列出的 5 种通知类型,如下所示。

环绕通知执行 - @Around

使用 @Around 注释声明环绕通知。它包含在匹配方法(JoinPoint)之前和之后执行的代码

通知方法的第一个参数必须是 ProceedingJoinPoint 类型。

在通知正文中,对 ProceedingJoinPoint 调用 proceed() 会导致执行底层方法。

在大多数情况下,从通知方法返回一个值很重要,因为匹配的 JoinPoint 可以有一个返回类型。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.ProceedingJoinPoint;
@Aspect
public class AroundExample {
    @Around("execution (* com.jbd.myapp.service.*.*(..))")
    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // Custom code before method execution
       Object retVal = pjp.proceed(); //method execution
        // Custom code after method execution
        return retVal; //exit the advice
    }
}

环绕通知的例子——@Around

问题陈述——我们将使用之前示例中的相同 UserRepository 并添加一个新功能来跟踪方法完成其执行所花费的时间。使用 @ExecutionTime 注释的任何方法都将在控制台中记录总执行时间。 @ExecutionTime 是一个自定义注解。

步骤 1:添加依赖项

创建一个maven项目,在pom.xml中添加如下依赖

<dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-core</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <!-- Step-1 Add the dependencies -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-aop</artifactId>
      <version>5.2.2.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.aspectj</groupId>
      <artifactId>aspectjweaver</artifactId>
     <version>1.9.5</version>
    </dependency>
<dependencies>

步骤 2:创建示例类

我们将创建带有@ExecutionTime注释的方法。所以我们创建了示例代码——UserRepository、配置类和注释,如下所示。正如我们之前讨论过的,注解@EnableAspectJAutoProxy 用于启用Spring-AOP。

UserRepository.java

package com.jbd.saop.around.dao;
import com.jbd.saop.around.ExecutionTime;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
  //Add a user
  @ExecutionTime
  public UserRepository add(String username) throws InterruptedException {
    Thread.sleep(100);
    if(username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("New user added: " + username);
    return this;
  }
  //Update an user
  public UserRepository update(String username, String email) {
    System.out.println("Update email: " + email);
    return this;
  }
  //Delete an user
  @ExecutionTime
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}

@ExecutionTime

package com.jbd.saop.around;
import java.lang.annotation.*;
/**
 * Tracks the execution time.
 */
//Allowed to use only on methods
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ExecutionTime {
}

ApplicationConfig.java

package com.jbd.saop.around;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@ComponentScan("com.jbd.saop.around")
@EnableAspectJAutoProxy
public class ApplicationConfig {
}

步骤 3:创建切入点表达式和通知

切入点表达式匹配所有使用@ExecutionTime 注释的方法。如果任何方法抛出 RuntimeException,它将被记录。创建方面类,如下所示。

package com.jbd.saop.around.advice;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.context.annotation.Configuration;
import java.time.LocalTime;
@Configuration
@Aspect
public class ExecutionTimeLogger {
  @Around("@annotation(com.jbd.saop.around.ExecutionTime)")
  public Object logExecTime(ProceedingJoinPoint pJoinPoint){
    System.out.println("Before method: "
        + pJoinPoint.getSignature().toShortString());
    long beforeTime = System.currentTimeMillis();
    Object result = null;
    try {
       result = pJoinPoint.proceed();//Important
      //If method throws Exception or any error occurs
    } catch (Throwable throwable) {
      throwable.printStackTrace();
    }
    long afterTime = System.currentTimeMillis();
    System.out.println("Time taken to execute: "
        + (afterTime - beforeTime) + "ms");
    return result;
  }
}

第 4 步:测试示例

最后,我们将测试该示例以查看其输出。在运行测试用例之前,在 pom.xml 中添加测试依赖项。

<dependencies>
  ... 
   <!-- Test Dependencies-->
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-engine</artifactId>
      <version>5.5.2</version>
      <scope>test</scope>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-test</artifactId>
      <version>5.2.2.RELEASE</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
...
<!-- Important for Jupiter-engine -->
<build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-surefire-plugin</artifactId>
        <version>3.0.0-M3</version>
      </plugin>
    </plugins>
  </build>
</project>

添加测试依赖项后,在 src\test 中创建 Test 类。

package com.jbd.saop.around;
import com.jbd.saop.around.dao.UserRepository;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
@SpringJUnitConfig(ApplicationConfig.class)
public class TestAroundAdvice {
  @Autowired
  private UserRepository userRepository;
  @Test
  public void testNull(){
    Assertions.assertNotNull(userRepository);
  }
  @Test
  public void testAddUser() throws InterruptedException {
    userRepository.add("sample_username");
  }
  @Test
  public void testAddUserFailure() throws InterruptedException {
    userRepository.add(null);
  }
}

测试用例应通过控制台中的以下输出。顺序可能不同。我建议分别运行每个测试用例并检查输出。

Before method: UserRepository.add(..)
New user added: sample_username
Time taken to execute: 124ms

Before method: UserRepository.add(..)
java.lang.RuntimeException: username is null
	at com.jbd.saop.around.dao.UserRepository.add(UserRepository.java:14)
	at com.jbd.saop.around.dao.UserRepository$$FastClassBySpringCGLIB$$dcb57c42.invoke()
...
Caused by: java.lang.NullPointerException
	... 86 more
Time taken to execute: 120ms

如您所见,Spring AOP 中的 around 建议非常强大。您可以更好地控制匹配的方法执行,尤其是由于 ProceedingJoinPoint。永远记得在通知方法中调用proceed()并返回Object值。

版权声明:著作权归作者所有。

相关推荐

Node.js写内容到文件的通用方法

Node.js目前有三种方法写一个文件:1、fs.write(fd, buffer, offset, length, position, callback)这个方法是没有缓存的,需要等待回调才能确保将buffer写入磁盘。 2、fs.writeFile(filename, data, [encoding], 

Spring Boot使用springProfile实现Logback多环境的通用配置

在一个基于Spring boot开发的项目里,常常需要有多套环境的配置:开发,测试以及产品。这里给出一个logback的通用配置。在src/main/resources目录下创建配置文件logback-spring.xml,多环境的通用配置内容如下:<?xml version="1.0" encoding="UTF-8"?

你需要知道的6个Ruby数组方法

数组是编程的基本结构之一,这里介绍6个Ruby操作数组的方法。Map/Each这两种方法非常相似。 它们让我们对数组的每一项执行操作。示例:array = [1, 2, 3] effects = array.each{|x| # 根据x求值 } added = array.map

[译]我希望在开始编码时就已知道的工具

在科技领域,有数千种工具可供人们使用。你怎么知道从哪里开始?就像最近开始编码的人一样,这种倾盆大雨的信息太多了,无法筛选出来。 我发现自己安装了扩展,在我的开发周期中并没有真正帮助我,并且通常甚至阻碍了它。我绝不是专家,但随着时间的推移,我编制了一份已证明对我非常有用的工具清单。 如果你刚刚开始学习编程,这将有望为你提供一些指导。 如果你是一位经验丰富的开发人员,希望

应该知道Vue UI组件(2018年)

Element这是饿了么前端团队开源桌面端的Vue UI组件。Github star 24k+。Github地址:https://github.com/ElemeFE/element中文文档:http://element-cn.eleme.io/#/zh-CNVux国内开源的专注于移动设备端的UI组件,基于Vue和微信的WeUI开发。Github star&

Vuejs兄弟组件之间的通信通信

vuejs兄弟组件之间的通信可以使用事件发射器。最简洁的方式使用Vue的root实例来作为全局的事件仓库。示例ComponentAthis.$root.$emit('myevent', data); ComponentBmounted() {     this.$root.$on('myevent', data =&g

Android Studio 3 .gitignore的通用配置

#built application files*.apk*.ap_# files for the dex VM*.dex# Java class files*.class# generated filesbin/gen/# Windows thumbnail dbThumbs.db# OSX files.DS_Store# Android Studio*.iml.idea.gradlebuild

二叉树的中序遍历

中序遍历 中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。在遍历左、右子树时,仍然先遍历左子树,再访问根结点,最后遍历右子树。            R / A B / / C D E F 上面这个树的中序遍历顺

ES6中async的使用案例

在项目中有时会遇到异步操作的问题,async就是解决异步操作的终极操作。我会以终极三问(what,why,when)的形式来说明什么是async。由于这是第一篇文章不知道怎么写,有很大部分是借鉴阮一峰老的原文,事例将会从我的项目中摘取。 async是什么? 官方例子 官方文档 async相当于对Generator 函数的一个语法糖const fs = require

Spring Cloud集成ZooKeeper注册中心绑定指定ip,解决UnknownHostException

Spring Cloud集成Zookeeper作为注册中心,从网关Spring Cloud GateWay转发给微服务里,在微服务里看到发送请求过来是以主机别名为url,如主机别名是server1.cluster,那么访问的地址是http://server.cluster/xxx。在GateWay有时会导致找不到服务。报类似如下的错误:500 Server Error for HTTP GET "