在 Spring AOP 中组合和重用切入点表达式

在 Spring AOP 中组合和重用切入点表达式

在本文中,您将学习在Spring AOP 中组合和重用多个 Pointcut 表达式。我们在上一篇文章中只讨论了它的一些知识。组合意味着使用两个或多个由逻辑运算符分隔的表达式 - &&、||和 !

我已经包含了高级示例以及一个完整的示例,以使您对这个主题有很好的理解。

1、组合切入点表达式

您可以使用 AND – &&, OR – || 或者 NOT - !组合多个切入点表达式。您还可以按名称引用切入点表达式。以下示例显示相同:

@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {} 
@Pointcut("within(com.jsbd.trading..*)")
private void inTrading() {} 
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {} 

记住:

1、使用@Pointcut 声明切入点表达式只需要方法签名,不允许方法体。

2、方法名称成为切入点表达式名称。

3、如果将表达式保存在单独的类中,则需要完全限定名称才能在另一个切入点表达式中访问它们。

 

2、重用(共享)切入点表达式

您还可以将切入点表达式保存在一个单独的类 (Pointcut_class) 中,并使用 @Aspect 进行注释,并在具有完整限定名称的 Aspect 类中重用它们(例如 Pointcut_class.expression_name())。这样,您可以在多个方面方法中重用相同的表达式。我们将在下面显示的简单代码的帮助下看到这一点。

package com.jbd.someapp.aspect;
@Aspect //Only aspect annotation
public class SystemArchitecture {
    @Pointcut("within(com.jbd.someapp.web..*)")
    public void inWebLayer() {}
    @Pointcut("within(com.jbd.someapp.service..*)")
    public void inServiceLayer() {}
}
@Aspect
@Configuration
public class ExampleAspect {
  @Before("com.jbd.someapp.aspect.SystemArchitecture.inWebLayer()")
  public void logWebLayer(){
    System.out.print("Log in web layer");
  }
}

3一个完整的示例

这是一个完整详细的逐步示例,用于学习组合和重用连接点。到目前为止,我们只探索了@Before 建议,因此我们将在下面的示例中使用@Pointcut 和@Before。

问题陈述——下面的示例通过报告分析 API 来跟踪所有 DAO 操作。但是,所有 update() 方法都必须调用特殊的分析 api,这与其他操作调用的 api 不同。

步骤 1:添加依赖项

<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:示例 UserRepository

我们将方面应用于 DAO 类。所以我们创建了一个示例 User Dao 类和 Java 配置来注册这个类,如下所示。

UserRepository.java

package c.jbd.saop.cjp.dao;
import org.springframework.stereotype.Repository;
//A very stupid demo repository
@Repository
public class UserRepository {
  //Add a user
  public UserRepository add(String username){
    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
  public boolean delete(String username){
    if (username == null) {
      throw new RuntimeException("username is null", new NullPointerException());
    }
    System.out.println("User deleted: " + username);
    return true;
  }
}

ApplicationConfig.java

package c.jbd.saop.cjp;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
@Configuration
@EnableAspectJAutoProxy //Enable AspectJ
@ComponentScan("c.jbd.saop.cjp")
public class ApplicationConfig {
}

提醒一下,@EnableAspectJAutoProxy 用于在应用程序中启用 AspectJ 功能。

步骤 3:创建切入点表达式和方面

我将使用单个类 AnalyticsAdvice,而不是创建两个单独的类。在实际用例中,您应该尝试将表达式和方面保持在单独的类中。

package c.jbd.saop.cjp.aspect;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.context.annotation.Configuration;
@Configuration
@Aspect
public class AnalyticsAdvice {
  //All Dao classes
  @Pointcut("within(c.jbd.saop.cjp.dao.*)")
  private void allDao() {
  }
  // All update() methods
  @Pointcut("execution(* update(..))")
  private void updateMethod() {
  }
  //Special analytics for Update methods
  @Before("allDao() && updateMethod()")
  public void specialAnalytics(JoinPoint joinPoint) {
    System.out.println("Call special analytics for: "
        + joinPoint.getSignature());
  }
  //general analytics for other methods
  @Before("allDao() && !updateMethod()")
  public void generalAnalytics(JoinPoint joinPoint) {
    System.out.println("Call general analytics for:"
        + joinPoint.getSignature());
  }
}

如您所见,顾名思义,有 2 个切入点表达式:

allDao() – 用于匹配所有 DAO 类,使用 within 指示符 updateMethod() – 用于匹配具有任意数量参数的任何类的 update() 方法。

@Before("allDao() && updateMethod()") 方面与 dao 包内的所有 update() 方法匹配。这就是调用特殊分析 api 所需要的。

同样,切面@Before("allDao() && !updateMethod()") 匹配除更新方法之外的所有Dao 类方法。这用于调用通用分析 API。

 

第 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>

永远记得在使用 Junit-jupiter-* 时将 maven-surefire-plugin 升级到 3.0.0-M3 或更高版本。

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

package c.jbd.saop.cjp;
import c.jbd.saop.cjp.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 TestAnalytics {
  @Autowired
  UserRepository userRepository;
  @Test
  public void notNull(){
    Assertions.assertNotNull(userRepository);
  }
  @Test
  public void testAddUser(){
    userRepository.add("Alexa");
  }
  @Test
  public void testUpdateUser(){
    userRepository.update("Alexa", "alexa@aws.com");
  }
}

输出

Call general analytics for:UserRepository c.jbd.saop.cjp.dao.UserRepository.add(String)
New user added: Alexa

Call special analytics for: UserRepository c.jbd.saop.cjp.dao.UserRepository.update(String,String)
Update email: alexa@aws.com

正如您在上面的示例中看到的,在调用更新方法之前调用了特殊的分析 API,对于调用通用分析的所有其他 DAO 方法。

总结

本文介绍了如何在 Spring AOP 中组合和重用切入点表达式。

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

相关推荐

Angular入口组件(entry component)和声明式组件的区别

Angular的声明式组件和入口组件的区别体现在两者的加载方式不同。声明式组件是通过组件声明的selector加载入口组件(entry component)是通过组件的类型动态加载声明式组件声明式组件会在模板里通过组件声明的selector加载组件。示例@Component({   selector: 'a-cmp',   templat

Spring定时任务@Scheduled的cron表达式

基本语法cron表达式由6-7个时间域组成,每个时间域之间用空格隔开。格式:<秒> <分> <时> <日期> <月> <星期> <年份> 秒:取值范围0-59分:取值范围0-59时:取值范围0-23日期:每月的的几数,0-31月:取值范围1-12,或英文简

Python单个表达式合并字典的方法

有时需要把两个字典合并为一个新的字典。这里记录下以下三种使用单个表达式的来做合并的方式。方式一:自定义合并函数def merge_dictionaries(first_dict, second_dict): merged = first_dict.copy() merged.update(second_dict) return mergedd1 = { "A": "张三", "B":

Java 正则表达式不区分大小写

Java里使用正则表达式默认是区分大小写的,如果想要做不区分大小写匹配,有两种方式:表达式前加上前缀(?)在调用Pattern.compile()时,通过参数指定不区分大小写。表达式前加前缀(?)示例:Pattern pattern = Pattern.compile("(?)hello:\\s(.*)");调用Pattern.compile()时,通过参数指定不区分大小写示例:Pattern.c

Java14新特性:Switch表达式

Java 14正式发布switch表达式特性。在之前的两个 Java 版本Java12,Java13,switch特性只是预览版。新的switch表达式有助于避免一些bug,因为它的表达和组合方式更容易编写。switch新的表达式有两个特点:支持箭头表达式返回。支持yied和return返回值。Java 14之前switch语法:switch (day) { case MONDAY:

Python替代三元表达式

在Python里时没有三元表达式:条件表达式?表达式1:表达式2但有一些替代方案:true返回的值 if 条件表达式 else false返回的值value_when_true if condition else value_when_false示例:'Yes' if fruit == 'Apple' else 'No'通过赋值的方式:fruit = 'Apple'isApple = True i

Python swith表达式替代方案

Python里是没有像其他语言,如java里的switch...case这样的表达式。替代方案一:map下标def f(x): return { 'a': 1, 'b': 2, }[x]使用下标的方式,可以很简洁的获取想要的值。但这种方法有一个弊端,如果不存在时,不能返回一个默认的值。替代方案:使用if-elifif x == 'a': # 满足条件,

JavaScript正则表达式使用小手册

以下是JavaScript使用正则表达式的一个备忘录。测试正则表达式test()方法:用来测试字符串是否满足表达式。let testString = "My test string"; let testRegex = /string/; testRegex.test(testString);测试多个pattern可以使用或操作符(|)来连接多个表达式const regex = /yes|no|ma