65.9K
CodeProject 正在变化。 阅读更多。
Home

Spring AOP 入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (9投票s)

2010年5月31日

CPOL

14分钟阅读

viewsIcon

68137

downloadIcon

1477

本文将通过使用 Eclipse、Maven、Spring AOP 2.5 等工具,轻松展示 Spring AOP 的基础知识和用法。

目录

引言

这是我关于 Spring 面向切面编程的第二篇文章。本文将通过使用 Eclipse、Maven、Spring AOP 2.5 等工具,轻松展示 Spring AOP 的基础知识和用法。

等等!为什么选择 Spring AOP 而不是完整的 AspectJ?

“使用能工作的最简单的东西。Spring AOP 比使用完整的 AspectJ 更简单,因为它不需要将 AspectJ 编译器/织入器引入您的开发和构建过程。如果您只需要通知 Spring bean 上的操作执行,那么 Spring AOP 是正确的选择。如果您需要通知 Spring 容器未管理的(例如,通常的领域对象)对象,那么您需要使用 AspectJ。如果您希望通知除简单方法执行之外的连接点(例如,字段获取或设置连接点等),那么您也需要使用 AspectJ。” - 来自 http://static.springsource.org/spring/docs/2.5.x/reference/aop.html

阅读上述内容后,您将知道这个问题的答案!我们只需要一个非常简单的框架来开始 AOP。最佳选择是流行的那个(这样更容易找到可以帮助我们的人 ^_^”)。

让我们开始面向切面编程!但您需要知道,我只会使用 Spring XML 配置 – 这可能是困难的方式,但很有趣!

什么是面向切面编程

“面向切面编程包括将程序逻辑分解为不同的部分(所谓的关注点,功能的内聚区域)。所有编程范式都支持一定程度的通过提供抽象(例如,过程、模块、类、方法)将关注点分组和封装到独立、独立的实体中,这些抽象可用于实现、抽象和组合这些关注点。但有些关注点会逃避这些实现形式,它们被称为横切关注点,因为它们‘贯穿’程序中的多个抽象。” - 来自 http://en.wikipedia.org/wiki/Aspect-oriented_programming#Overview

不同的部分?抽象,多个抽象?横切关注点?现在您的头疼了,您难以理解 AOP – 别担心 – 这可以简化

AOP 就像 Perl、.NET、Java 等编程语言中的触发器。

这很简单,对吧?在 AOP 之路上看到一点模糊的微光了吗?

也许您已经学过一些 SQL,并且听说过过程和触发器

  • 在 SQL 中,您为表或视图创建触发器。
  • 您的触发器将针对特定操作(插入、更新等)。
  • 并在选定的时间执行(之前、之后等)

AOP 也有类似的东西

  • 您创建称为“切面”的东西。
  • 您的“切面”将通过检查称为“切入点”的内容来执行。
  • 也在选定的时刻执行(之前、之后、环绕等)。

这就是 AOP,但您需要了解 AOP 的术语和定义,例如 OO 风格(包、类、方法等)。

术语与定义

正如每个概念一样,AO 风格也有其术语,在开始之前您需要了解一些。

  • 关注点:这是一个战场,任何感兴趣的领域,任何用例(在 UML 中),任何让您感兴趣并希望改变的东西/区域,就像经典的用例“支付”、“用户管理”以及许多其他一样。
  • 横切关注点:这是一个可能影响整个应用程序的关注点,应尽可能集中在代码的一个位置,例如“身份验证”或“日志记录”。
  • 连接点:程序执行过程中的一个点,例如方法的执行或异常的处理。
  • 切面:一个封装关注点的模块。一个切面由切入点、通知体和类型间声明组成。在某些方法中,切面也可能包含类和方法。
  • 切入点:匹配连接点的谓词。
  • 通知:切面在特定连接点的期望时刻采取的行动——“之前”、“之后”等。

这些不是 AO 风格的所有术语,只是我们开始时需要的。有关更多 Spring AOP 术语,您可以查阅 http://static.springsource.org/spring/docs/2.5.x/reference/aop.html

切入点!它是什么?

切入点是匹配连接点的谓词。这与模式匹配(对于 Perl 程序员)或正则表达式非常相似。这个谓词或表达式匹配,例如,一个特定名称的方法。示例(匹配所有名称以“list”开头的方法的切入点)

execution ( * adrabi.codeproject.*.GamesImpl.list*( .. ) )

GameImpl 中所有名称以“list”开头且带有任何类型参数的方法。

/!\ 噢!我忘了告诉您,Spring AOP 仅限于 bean 中的公共方法,与 AspectJ 不同。您对此没有问题,对吗?

说真的,切入点是切面中重要的一部分。没有匹配正确的切入点,通知就无法触发或执行,否则您将得不到期望的结果。

但是,等等!你为什么一开始就写 execution( )?你能给我们一个真实的例子吗?

首先,这是一个真实的例子

<aop:config> 
    <aop:pointcut id="gamesService" 
       expression="execution(* com.xyz.myapp.service.*.*(..))" /> 
</aop:config>

其次,在我们的情况下,execution() 是必要的。它是一种切入点类型或(切入点指示器)。此外,切入点不限于单个 execution()。您可以使用 AND “&&”,OR “||”,和 Negated “!” 来组合多个。惊讶吗?

现在是时候为开始 Spring AOP 做准备,配置您的环境了。

环境配置

终于!真实的例子开始生效了。尝试准备一杯美味的咖啡回来!

您需要按照这篇文章安装 Maven 和 SpringIDE 在 Eclipse 中:“Pivot 1.4, Spring and Hibernate is my RPG Game” - https://codeproject.org.cn/KB/java/PivotSpringHibernateGame.aspx(您只需要阅读环境配置部分,然后获胜归来)。

现在您知道如何在 Eclipse 中安装任何东西了 – 非常好 – 您需要安装 Eclipse TDTD 用于监控和视图执行流程。它不是必需的,但理解它的最佳方法是查看“对象”如何交互。

安装 Eclipse TDTD

TDTD 类的交互示例

这一点也不难。您只需在您的大脑中打开配置文件,并将变量“use_imagination = 0”更改为“use_imagination = 1” :D 开玩笑。

现在,开始编码。

通知!之前、之后和环绕

配置完成后,我们将开始编写示例代码。对于每个示例,我们创建一个唯一的项目,并使用“execute()”切入点类型。我们稍后将看到其他类型。通过使用 Maven,我们不需要为每个项目进行特定配置;相同的配置可以用于所有项目。

此外,您需要知道如何创建 Maven 项目并将其添加为 Spring 项目类型(https://codeproject.org.cn/KB/java/PivotSpringHibernateGame.aspx)

这是所有项目的 POM 配置

<build> 
    <plugins> 
        <plugin> 
            <groupId>org.apache.maven.plugins</groupId> 
            <artifactId>maven-compiler-plugin</artifactId> 
            <configuration> 
                <target>1.5</target> 
                <source>1.5</source>
            </configuration> 
        </plugin> 
    </plugins> 
</build> 
<dependencies> 
    <dependency> 
        <groupId>org.springframework</groupId> 
        <artifactId>spring-aop</artifactId> 
        <version>2.5.6</version> 
    </dependency> 
    <dependency> 
        <groupId>org.springframework</groupId> 
        <artifactId>spring-context</artifactId>
        <version>2.5.6</version> 
    </dependency>
    <dependency>
        <groupId>aspectj</groupId> 
        <artifactId>aspectjweaver</artifactId> 
        <version>1.5.4</version> 
        <scope>compile</scope> 
    </dependency> 
</dependencies>

前置通知

我们做什么?

我们使用以下方法模拟一个简单的硬件 CRUD 应用程序:

  • 一个用于服务的接口:HardwareService.java
  • 一个用于实现接口的类:HardwareImpl.java
  • 一个用于实体类的简单类:Hardware.java
  • 一个用于创建切入点方法的类:HardwareServicePointcuts.java
  • 以及一个用于执行应用程序的类:Main.java
  • 加上 Spring 配置文件:application.xml

目的是对 Hardware 服务(HardwareImpl.java)应用三个“前置”通知的切面。我们研究结果,每个切面都有一个唯一的切入点,使用 Hardware 服务切入点(HardwareServicePointcuts.java)中的一个唯一方法。切入点的目标是:

  • 切入点,用于在 Hardware 服务中的任何方法开始执行时触发名为“beforeAnyMethodStartedWithList”的方法,并且方法名以“list”开头。
  • 切入点,用于在方法“remove”开始执行时触发名为“beforeRemove”的方法。
  • 切入点,用于在 Hardware 服务中任何未命名为“save”的方法开始执行时触发名为“beforeIfNotSaveMethod”的方法。

目标清楚了吗?让我们研究结果和输出,但在我们这样做之前,我只粘贴感兴趣的部分来源。

public interface HardwareService 
{ 
      /** 
      * Save new Hardware 
      * @param hw 
      * @return 
      */ 
      public Hardware save( Hardware hw ); 
      
      /** 
      * Remove Hardware 
      * @param hw 
      */ 
      public void remove( Hardware hw ); 
      
      /** 
      * Get Hardware by id 
      * @param id 
      * @return 
      */ 
      public Hardware get(int id); 
      
      /** 
      * Get list of all Hardware 
      * @return 
      */ 
      public List<Hardware> listAll(); 
      
      /** 
      * Get list of Hardware by vendor 
      * @param vendor 
      * @return 
      */ 
      public List<Hardware> listByVendor(String vendor); 
}

这是 Hardware 服务(HardwareService.java

public class HardwareServicePointcuts 
{ 
      /** 
      * Pointcut for any method in bean Hardware service his name started with 'list' 
      */ 
      public void beforeAnyMethodStartedWithList() 
      { 
        System.out.println("*** Some methods started with 'list' has " + 
                           "begin executing now! ***"); 
      } 
      
      /** 
      * Pointcut for method remove only in bean Hardware serive 
      */ 
      public void beforeRemove() 
      { 
        System.out.println("*** Method 'remove' has begin executing now! ***"); 
      } 
      
      /** 
      * Pointcut for any method not named 'save' in bean hardware service 
      */ 
      public void beforeIfNotSaveMethod() 
      { 
        System.out.println( "*** Method 'save' hasn't begin executing yet! ***" ); 
      } 
}

这是当切入点匹配时将触发的方法。

<aop:config> 
    <aop:aspect id="anyMethodStartedWithList" ref="hardwareServicePointcuts"> 
        <aop:before method="beforeAnyMethodStartedWithList" 
            pointcut="execution(* adrabi.codeproject.*.*.*.HardwareImpl.list*(..) )" /> 
    </aop:aspect> 

    <aop:aspect id="MethodRemove" ref="hardwareServicePointcuts"> 
        <aop:before method="beforeRemove" 
           pointcut="execution(* adrabi.codeproject.*.*.*.
                     HardwareImpl.remove(adrabi.*.*.*.*.Hardware) )" /> 
    </aop:aspect> 

    <aop:aspect id="notMethodSave" ref="hardwareServicePointcuts"> 
        <aop:before method="beforeIfNotSaveMethod" 
           pointcut="!execution(* adrabi.codeproject.*.*.*.HardwareImpl.save(..) )" /> 
    </aop:aspect> 
</aop:config>

这是 Spring AOP 的配置。您会注意到三个切面。每个切面都有一个“前置”通知,每个通知都有一个切入点。每个切入点都试图匹配某些内容。

pointcut="!execution(*adrabi.codeproject.*.*.*.HardwareImpl.save(..) )"

您注意到“execution(..)”前面的否定号“!”了吗?这证实了我在前几章中提到的内容。

现在我们剖析应用程序 main(在 Main 类中,右键单击 > 在上下文菜单中 > 单击 Profile As > Java Application)。

public static void main(String[]$) 
{ 
    //init Spring configuration 
    ApplicationContext context = 
      new ClassPathXmlApplicationContext("application.xml"); 
    HardwareService hds = (HardwareService) context.getBean("hardwareService"); 
    
    /*###############[ -- any method started with 'list' -- ]#################*/ 
    System.out.println("\n\n###############[ -- any method " + 
                       "started with 'list' -- ]#################"); 
    //~ 
    List<Hardware> list = hds.listAll(); 
    for( Hardware h : list ) 
    { 
                  System.out.println( h ); 
    } 
    //~ 

    /*###############[ -- method named 'remove' -- ]#################*/ 
    System.out.println("\n\n###############[ -- method " + 
                       "named 'remove' -- ]#################"); 
    //~ 
    hds.remove( hds.get(2) ); 
    //~ 
    
    
    /*###############[ -- method named 'save' -- ]#################*/ 
    System.out.println("\n\n###############[ -- method " + 
                       "named 'save' -- ]#################"); 
    //~ 
    hds.save( new Hardware(4, 5454477877213l, "vendor #4") ); 
    for( Hardware h : list ) 
    { 
                  System.out.println( h ); 
    } 
    //~ 
}

剖析成功完成后,我们会得到一个很好的输出,我们可以与类交互进行比较。

###############[ -- any method started with 'list' -- ]################# 
*** Some methods started with 'list' has begin executing now! *** 
*** Method 'save' hasn't begin executing yet! *** 

Hardware :: Identify [1], Serial number [1234567890254] and Vendor [vendor #1] 
Hardware :: Identify [2], Serial number [6545665661545] and Vendor [vendor #2] 
Hardware :: Identify [3], Serial number [1545422984548] and Vendor [vendor #3] 

###############[ -- method named 'remove' -- ]################# 
*** Method 'save' hasn't begin executing yet! *** 
*** Method 'remove' has begin executing now! *** 
*** Method 'save' hasn't begin executing yet! *** 
 
###############[ -- method named 'save' -- ]################# 
Hardware :: Identify [1], Serial number [1234567890254] and Vendor [vendor #1] 
Hardware :: Identify [3], Serial number [1545422984548] and Vendor [vendor #3] 
Hardware :: Identify [4], Serial number [5454477877213] and Vendor [vendor #4]

现在我们用“类交互”打开剖析报告。

我们比较第一个切面“任何以 'list' 开头的方法”,这是序列图的一部分。

从图表中,我们得出结论,匹配率为 100.1% 的输出结果。您可以看到“beforeAnyMethodStartedWithList”和“beforeIfNotSaveMethod”这两个方法在 HardwareImpl 类中的“listAll”方法之前执行。

请记住,我们为 bean/类 HardwareImplHardwareImpl.java)创建了切面。

/!\ 噢!我忘了告诉您,Spring 按配置文件中的顺序触发和/或执行切面。

如果我们有以下配置,结果将与上次剖析不同。试试看,您会看到不同之处。

<aop:config> 
    <aop:aspect id="MethodRemove" ref="hardwareServicePointcuts"> 
        <aop:before method="beforeRemove" 
          pointcut="execution(* adrabi.codeproject.*.*.*.
                    HardwareImpl.remove(adrabi.*.*.*.*.Hardware) )" /> 
    </aop:aspect> 
    
    <aop:aspect id="notMethodSave" ref="hardwareServicePointcuts"> 
        <aop:before method="beforeIfNotSaveMethod" 
           pointcut="!execution(* adrabi.codeproject.*.*.*.HardwareImpl.save(..) )" /> 
    </aop:aspect> 

    <aop:aspect id="anyMethodStartedWithList" ref="hardwareServicePointcuts"> 
        <aop:before method="beforeAnyMethodStartedWithList" 
          pointcut="execution(* adrabi.codeproject.*.*.*.HardwareImpl.list*(..) )" /> 
    </aop:aspect> 
</aop:config>

也许,其余的都很好很清楚?我只想澄清为什么我们两次为名为“remove”的方法使用切面。

###############[ -- method named 'remove' -- ]################# 
*** Method 'save' hasn't begin executing yet! *** 
*** Method 'remove' has begin executing now! *** 
*** Method 'save' hasn't begin executing yet! ***

正如您在 Main 类中看到的

/*###############[ -- method named 'remove' -- ]#################*/ 
System.out.println("\n\n###############[ -- method " + 
                   "named 'remove' -- ]#################"); 
//~ 
hds.remove( hds.get(2) ); 
//~

请仔细而缓慢地阅读这段代码:“hds.remove( hds.get(2) );”。这里我们有两个方法:第一个是“get”,第二个是“remove”。“get”被触发是因为它不叫“save”和“remove”,触发了切面 n°3。

下一个通知与前一个相反,我们称之为“after”。

后置通知

我们做什么?

我们使用以下方法模拟一个简单的身份验证系统:

  • 一个用于服务的接口:AuthenticationService.java
  • 一个用于实现接口的类:AuthenticationImpl.java
  • 一个用于创建切入点方法的类:AuthenticationServicePointcuts.java
  • 以及一个用于执行应用程序的类:Main.java
  • 加上 Spring 配置文件:application.xml

目标与“前置”通知类似,我们只有两个切面和两个切入点。

  • 切入点,用于在方法“userName”或“password”完成执行后触发名为“afterUsernameOrPassword”的方法。
  • 切入点,用于在 Authentication 服务中的任何名为“logout”的方法完成执行后触发名为“afterLogout”的方法。

我们执行一些步骤,就像在“前置”通知中一样。

public interface AuthenticationService 
{ 
  /** 
  * Setting a user name for authentication 
  * @param userName 
  */ 
  public void userName(String userName); 
  
  /** 
  * Setting a password for authentication 
  * @param password 
  */ 
  public void password(String password); 
  
  /** 
  * Try to log-in 
  * @return 
  */ 
  public boolean login(); 
  
  /** 
  * Try to log-out 
  * @return 
  */ 
  public boolean logout(); 
}

这是 Authentication 服务

public class AuthenticationServicePointcuts 
{ 
  /** 
  * Pointcut for methods userName() or password() 
  */ 
  public void afterUsernameOrPassword() 
  { 
      System.out.println("*** username or password method has exected ***"); 
  } 
  
  /** 
  * Pointcut for method logout 
  */ 
  public void afterLogout() 
  { 
      System.out.println( "*** you've logout now! see you later, bye! ***" ); 
  } 
}

这是当切入点匹配时将触发的方法。

<aop:config> 
    <aop:pointcut id="p_usernameOrPassword" 
       expression="execution(* userName(..)) or execution(* password(..))" /> 
    <aop:pointcut id="p_logout" expression="execution(* logout())" /> 

    <aop:aspect id="afterUsernameOrPassword" ref="authServicePointcuts"> 
       <aop:after method="afterUsernameOrPassword" pointcut-ref="p_usernameOrPassword"/> 
    </aop:aspect> 

    <aop:aspect id="afterLogout" ref="authServicePointcuts"> 
       <aop:after method="afterLogout" pointcut-ref="p_logout"/> 
    </aop:aspect> 
</aop:config>

您可以看到两件事发生了变化。

  • 首先,我们创建了一个单独的切入点。
  • 其次,在“after”通知中,我们不使用“pointcut”参数,而是用“pointcut-ref”替换它,就像在“前置”通知中一样。

另外,您会注意到在切入点 n°1“usernameOrPassword”中,我使用了“or”。

expression="execution(* userName(..)) or execution(* password(..))"

Spring AOP 支持“OR”和“||”表达式;您可以选择。

我们剖析主应用程序。

public static void main(String[]$) 
{ 
    //init Spring configuration 
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); 
    AuthenticationService auth = (AuthenticationService)context.getBean("authService"); 

    auth.userName("adrabi"); 
    auth.password("no-password"); 
    System.out.println( "Login results is : " + auth.login()); 
    System.out.println( "Logout results is : " + auth.logout()); 
}

我们得到一个很好的输出。

*** username or password method has exected *** 
*** username or password method has exected *** 
Login results is : true 
*** you've logout now! see you later, bye! *** 
Logout results is : true 

现在,我们用“UML 2 类交互”打开剖析报告,我们可以看到序列图的这一部分。

完美,与输出非常相似。我们可以看到在“userName”和“password”这两个方法中,“afterUsernameOrPassword”方法在执行完成后被触发。

我们跳到最后一个通知,但不是最后一个。

环绕通知

我们做什么?

我们使用以下方法模拟一个简单的串行系统破解:

  • 一个用于服务的接口:SerialService.java
  • 一个用于实现接口的类:SerialImpl.java
  • 一个用于创建切入点方法的类:SerialServicePointcuts.java
  • 以及一个用于执行应用程序的类:Main.java
  • 加上 Spring 配置文件:application.xml

目标与“前置”通知类似,我们使用两个切入点来破解序列号系统。

  • 切入点,用于触发名为“aroundCheckSerialNumber”的方法来替换名为“checkSerialNumber”的方法。
  • 切入点,用于触发名为“aroundSendHasActived”的方法来替换名为“sendSerialHasActived”的方法。

我们执行一些步骤,就像在“前置”通知中一样。

public interface SerialService 
{ 
  /** 
  * Check serial number is valid 
  * @param serial 
  * @return 
  */ 

  public boolean checkSerialNumber(String serial); 
  
  /** 
  * Send serial is actived to vendor 
  */ 
  public void sendSerialHasActived(); 
}

这是 Serial 服务。

public class SerialServicePointcuts 
{ 
  /** 
  * Around and Stop "CheckSerialNumber" method to be executed 
  * 
  * @param joinPoint 
  */ 
  public boolean aroundCheckSerialNumber( ProceedingJoinPoint joinPoint ) 
  { 
    System.out.println( "*** The method [" + joinPoint.getSignature().getName() + 
                        "] has been cracked ^_^ ***" ); 
    return true; 
  } 
  
  /** 
  * Around and Stop "SendIsHasActived" method to be executed 
  * 
  * @param joinPoint 
  */ 
  public void aroundSendHasActived(ProceedingJoinPoint joinPoint) 
  { 
    System.out.println( "*** The method [" + 
                        joinPoint.getSignature().getName() + 
                        "] has been cracked ^_^ ***" ); 
  }
}

这是当切入点匹配时将触发的方法。

<aop:config> 
    <aop:aspect id="aroundCheckSerialNumber" ref="serialServicePointcuts"> 
        <aop:around method="aroundCheckSerialNumber" 
                pointcut="execution(* checkSerialNumber(..))"/> 
    </aop:aspect> 
    <aop:aspect id="aroundSendHasActived" ref="serialServicePointcuts"> 
        <aop:around method="aroundSendHasActived" 
                pointcut="execution(* sendSerialHasActived(..))"/> 
    </aop:aspect> 
</aop:config>

这是我们邪恶的 Spring AOP 配置 ^_^,用于破解序列号系统并阻止序列号发送给供应商。

您也可以注释掉它,看看结果会带来什么惊喜。

现在我们剖析 Main 类。

public static void main(String[]$) 
{ 
    //init Spring configuration 
    ApplicationContext context = new ClassPathXmlApplicationContext("application.xml"); 
    SerialService serial = (SerialService) context.getBean("serialService"); 
    
    //serial for test 1893-5714-6364 
    if( serial.checkSerialNumber(null) ) 
    { 
        serial.sendSerialHasActived(); 
    } 
}

我们得到一个非常好的输出。

*** The method [checkSerialNumber] has been cracked ^_^ ***
*** The method [sendSerialHasActived] has been cracked ^_^ ***

赶快查看类交互。

您可以看到在序列图中,“checkSerialNumber”和“sendSerialHasActived”这两个方法已被“aroundCheckSerialNumber”和“aroundSendHasActived”替换。很酷,对吧?我个人更喜欢“环绕”通知。

我希望您喜欢经典的“前置”、“后置”和“环绕”通知。

其他通知

我太累了,无法为最后两个通知“返回后”和“抛出后”创建更多项目。您现在知道如何创建自己的切面,现在您也了解了 Spring AOP。我只会介绍它们。

  • “返回后”通知:一个连接点正常完成(例如,方法在不抛出异常的情况下返回)后触发/执行的通知。
  • “抛出后”通知:方法因抛出异常而退出时触发/执行的通知。

这很容易理解:“返回后”表示方法成功“返回”之后,“抛出后”表示方法因任何异常退出后。但是,“后”通知放在它们之间有什么意义?“后”通知是一个完整完成的方法;换句话说,

“后”通知 = “返回后”通知 + “抛出后”通知。

现在您可以利用前面的项目创建自己的示例,请参考文档。

战场上的参数

到目前为止,我们从未介绍过 AOP 中的参数,现在是时候稍微讨论一下它们了。我们可以将参数从连接点传递到被触发/执行的方法吗?是的,我们可以轻松做到!我将向您展示两种传递参数的方法。

我们是通过 Spring XML 配置传递它们吗?通常,我们使用 Spring XML 配置。我们该怎么做?

我们使用以下方法模拟一个简单的漫画和动画剧集搜索引擎,并保存查询:

  • 一个用于服务的接口:SearchService.java
  • 一个用于实现接口的类:SearchImpl.java
  • 一个用于创建切入点方法的类:SearchServicePointcuts.java
  • 以及一个用于执行应用程序的类:Main.java
  • 加上 Spring 配置文件:application.xml

使用两个链接到“后”通知的切入点,将参数从连接点传递到被触发/执行的方法。

  • 切入点,用于在方法“bingSearch”完成执行后触发名为“afterBingSearch”的方法。
  • 切入点,用于在 Authentication 服务中的任何名为“googleSearch”的方法完成执行后触发名为“afterGoogleSearch”的方法。
public interface SearchService 
{ 
  /** 
  * Bing search 
  * 
  * @param query 
  * @param cookie 
  */ 
  public void bingSearch(String query, String cookie); 

  /** 
  * Google search 
  * 
  * @param query 
  * @param cookie 
  * @param userId 
  */ 
  public void googleSearch(String query, String cookie, String userId); 
}

这是 Search 服务源代码。

/** 
* Save Bing search query 
* 
* @param jp 
*/ 
public void afterBingSearch(JoinPoint jp) 
{ 
    System.out.println( "*** Save Bing search : [query : " + jp.getArgs()[0] + 
                        ", cookie : " + jp.getArgs()[1] + "] ***" ); 
} 

/** 
* Save Google search query 
* 
* @param query 
* @param cookie 
* @param userId 
*/ 
public void afterGoogleSearch(String query, String cookie, String userId) 
{ 
    System.out.println( "*** Save Bing search : [query : " + query + 
                        ", cookie : " + cookie + ", userid : " + userId + "] ***" ); 
}

这是切入点的源代码。

<aop:config> 
    <aop:aspect id="afterBingSearch" ref="seachServicePointcuts"> 
        <aop:after method="afterBingSearch" pointcut="execution(* bingSearch(..))"/> 
    </aop:aspect> 

    <aop:aspect id="afterGoogleSearch" ref="seachServicePointcuts"> 
        <aop:after method="afterGoogleSearch" 
          pointcut="execution(* googleSearch(..)) and args(query,cookie,userId)"/> 
    </aop:aspect> 
</aop:config>

我们尝试以两种方式传递参数。

  • 首先,通过使用“bingSearch”方法的参数类型“JoinPoint”。
  • 其次,通过在“googleSearch”方法中使用 `args(..)` 绑定参数。

这次没有剖析,我们只需要运行我们的 Main 类。

public static void main(String[] $) 
{ 
    // init Spring configuration 
    ApplicationContext context = new ClassPathXmlApplicationContext( 
                                "application.xml"); 
    SearchService search = (SearchService) context.getBean("searchService"); 
    
    search.bingSearch("ONE PIECE", "some-cookie"); 
    search.googleSearch("Bleach", "some-cookie", "no-userId"); 
}

最后,我们得到一个很好的输出,您可以看到参数确实已从连接点传递到被触发的方法。

One Piece 452 
One Piece 451 
*** Save Bing search : [query : ONE PIECE, cookie : some-cookie] *** 
Bleach Episode 271 
Bleach Episode 270 
*** Save Bing search : [query : Bleach, cookie : some-cookie, userid : no-userId] ***

我将通过向您展示其他切入点类型来结束本文。

其他切入点类型列表

这里是您可以在 XML 配置中使用切入点的其余列表。

  • within:将匹配的连接点限制在特定类型内(在使用 Spring AOP 时,仅限于在匹配类型中声明的方法的执行)。
  • this:限制匹配的连接点(在使用 Spring AOP 时的方法执行),其中 bean 引用(Spring AOP 代理)是给定类型的实例。
  • target:限制匹配的连接点(在使用 Spring AOP 时的方法执行),其中目标对象(被代理的应用程序对象)是给定类型的实例。

结论

本文的目的是向您展示如何使用 Spring AOP 并让您熟悉它。Spring AOP 还可以使用“注解”并支持 AspectJ。它不限于 XML 配置。我只是想只使用 XML。

  • AOP 只是触发器。
  • Spring AOP 可以使用五种类型的通知(前置、后置、环绕、返回后和抛出后)。
  • Spring AOP 不限于 XML 配置。

我将让您自己探索 Spring AOP 注解。感谢您热情阅读我的文章,并感谢任何更正和建议。

© . All rights reserved.