vlambda博客
学习文章列表

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

在你开始阅读本章之前,我想和你分享一些东西;在我撰写本章时,我的妻子 Anamika 正在自拍并将其上传到 Facebook 和 WhatsApp 等多个社交媒体网站。她会跟踪,但是,上传更多照片会使用更多移动数据,而移动数据需要花钱。我很少使用社交媒体,因为我宁愿避免向互联网公司支付更多费用。每个月,互联网公司都知道要向我们收取多少费用。现在考虑如果我们精心规划和管理互联网使用、总通话时长和账单计算会发生什么?一些痴迷的互联网用户可能会管理它,我真的不知道如何。

计算互联网使用和通话计费是一项重要功能,但对于大多数互联网用户来说仍然不是主要功能。对于像我妻子这样的人来说,自拍、上传照片到社交媒体、在 YouTube 上观看视频是大多数互联网用户积极参与的事情。管理和计算他们的互联网账单是互联网用户的被动行为。

同样,企业应用程序的某些模块就像我们使用互联网的互联网计费计算器。应用程序中有一些具有重要功能的模块需要放置在应用程序的多个位置。但是在每个点显式调用这些功能是出乎意料的。日志记录、安全性和事务管理等功能对您的应用程序很重要,但您的业务对象并没有积极参与其中,因为您的业务对象需要专注于他们设计的业务领域问题,并留下某些方面有待处理由别人。

在软件开发中,在应用程序中的某些 点需要执行特定的任务。这些任务或功能称为横切关注点。在一个应用程序中,所有横切关注点都与该应用程序的业务逻辑分开。 Spring 提供了一个模块 Aspect-Oriented Programming (AOP)将这些横切关注点与业务逻辑分开。

第4章使用依赖注入模式连接Bean ,您了解了依赖注入来配置和解决应用程序中协作对象的依赖关系。 DI 促进编程以接口和解耦应用程序对象,而 Spring AOP 促进应用程序的业务逻辑 应用程序中的横切关注点之间的解耦。

在我们的 bankapp 示例中,将资金从一个帐户转移到另一个帐户是一种业务逻辑,但记录此活动并保护交易是我们 bankapp 应用程序中的横切关注点。这意味着日志记录、安全性和事务是方面应用的常见示例。

在本章中,您将探索 Spring 对方面的支持。它将涵盖以下几点:

  • Proxy pattern in Spring
  • Adapter design pattern to handle load time weaving
  • Decorator design pattern
  • Aspect-oriented programming
  • Problems resolved by AOP
  • Core AOP concepts
  • Defining point cuts
  • Implementing Advices
  • Creating aspects
  • Understanding AOP proxies

在深入讨论 Spring AOP 之前,让我们先了解一下 Spring AOP Framework 下实现的模式,看看这些模式是如何应用的。

Spring中的代理模式


代理设计模式提供了一个类的object,它具有另一个类的功能。该模式属于 GOF 设计模式的结构设计模式。根据 GOF 模式,为另一个对象提供代理或占位符以控制对其的访问。此设计模式的目的是为另一个类提供一个不同的类,并为外部世界提供功能。

在 Spring 中使用装饰器模式代理类

正如您在 第 3 章中看到的,结构和行为模式的考虑,根据 GOF 的书,动态地为对象附加额外的职责。装饰器为扩展功能提供了一种灵活的替代子类化的方法。此模式允许您在运行时动态或静态地向单个 object 添加和删除行为,而无需更改现有行为来自同一类的其他关联对象。

在 Spring AOP 中,CGLIB 用于在应用程序中创建代理。 CGLIB 代理通过在运行时生成目标类的子类来工作。 Spring 将这个生成的子类配置为将方法调用委托给原始目标——子类用于实现装饰器模式,编织在通知中。

Spring 提供了两种在应用程序中创建代理的方法。

  • CGLIB proxy
  • JDK proxy or dynamic proxy

我们看下表:

JDK 代理

CGLIB 代理

也称为 动态代理

未内置在 JDK 中

API 内置在 JDK 中

包含在 Spring JAR 中

要求:Java 接口

接口不可用时使用

代理的所有接口

不能应用于最终类或方法

我们来看下图:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

笔记

注意--CGLIB 代理有一个问题需要考虑,即不能建议最终方法,因为它们不能被覆盖。

在下一节中,让我们更多地了解横切关注点。

什么是横切关注点?


在任何应用程序中,很多地方都需要一些 generic 功能。但此功能与应用程序的业务逻辑无关。假设您在应用程序中的每个业务方法之前执行基于角色的安全检查。在这里,安全是一个跨领域的关注点。任何应用程序都需要它,但从业务角度来看不是必需的,它是我们必须在应用程序的许多地方实现的简单通用功能。以下是企业应用程序的横切关注点的示例。

  • Logging and tracing
  • Transaction management
  • Security
  • Caching
  • Error handling
  • Performance monitoring
  • Custom business rules

让我们看看我们将如何使用 Spring AOP 的各个方面在我们的应用程序中实现这些横切关注点。

什么是面向方面的编程?


如前所述,Aspect-Oriented Programming (AOP)横切关注点的模块化。它补充了 面向对象编程 (OOP),这是另一个编程范式。 OOP 将类和对象作为关键元素,但 AOP 将方面作为关键元素。方面允许您在多个点跨应用程序模块化一些功能。这种类型的功能称为横切关注点。例如,安全性是应用程序中的横切关注点之一,因为我们必须在需要安全性的多种方法中应用它。同样,事务 日志记录也是应用程序 更多的横切关注点。让我们在下图中看看这些关注点是如何应用于业务模块的:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如上图所示,主要业务模块有TransferServiceAccountService<三个/strong>BankService。所有业务模块都需要一些通用功能,例如 SecurityTransaction管理和日志记录

让我们看看如果我们不使用 Spring AOP,我们在应用程序中会遇到哪些问题。

AOP 解决的问题

如前所述,方面启用横切关注点的模块化。因此,如果您不使用方面,那么某些横切功能的模块化是不可能的。它倾向于将横切功能与业务模块混合。如果您使用通用的面向对象原则来重用安全、日志记录和事务管理等通用功能,您需要使用继承或组合。但是这里使用继承会违反 SOLID 原则的单一职责,并且还会增加对象层次结构。此外,在整个应用程序中处理组合可能很复杂。这意味着,未能模块化横切关注点会导致以下两个主要问题:

  • Code tangling
  • Code scattering

代码纠缠

它是应用程序中关注的耦合。当横切关注点与应用程序的业务逻辑混合时,就会发生代码缠结。它促进了横切模块和业务模块之间的紧密耦合。让我们看看下面的代码来了解更多关于代码纠缠的信息:

    public class TransferServiceImpl implements TransferService { 
      public void transfer(Account a, Account b, Double amount) { 
        //Security concern start here 
        if (!hasPermission(SecurityContext.getPrincipal()) { 
          throw new AccessDeniedException(); 
        } 
        //Security concern end here 
          
        //Business logic start here 
        Account aAct = accountRepository.findByAccountId(a); 
        Account bAct = accountRepository.findByAccountId(b); 
        accountRepository.transferAmount(aAct, bAct, amount); 
        ... 
      } 
    } 

正如您在前面的代码中看到的,安全问题代码(突出显示)与应用程序的业务逻辑代码混合在一起。这种情况是代码纠结的一个例子。这里我们只包含了安全关注点,但是在企业应用程序中你必须实现多个横切关注点,例如日志记录、事务管理等。在这种情况下,管理代码和对代码进行任何更改都会更加复杂,这可能会导致代码出现严重错误,如图所示:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

在上图中,您可以看到三个横切关注点分布在 TransferService 业务类中,横切关注点逻辑与 AccountService 的业务逻辑。关注点和应用程序逻辑之间的这种耦合称为 code tangling。如果我们将方面用于横切关注点,让我们看看另一个主要问题。

代码分散

这意味着 same 关注点分布在应用程序的各个模块中。代码分散促进了应用程序模块中关注点代码的重复性。让我们看下面的代码来了解更多关于代码散射的信息:

    public class TransferServiceImpl implements TransferService { 
      public void transfer(Account a, Account b, Double amount) { 
        //Security concern start here 
        if (!hasPermission(SecurityContext.getPrincipal()) { 
          throw new AccessDeniedException(); 
        } 
        //Security concern end here 
          
        //Business logic start here 
        ... 
      } 
    } 
 
    public class AccountServiceImpl implements AccountService { 
      public void withdrawl(Account a, Double amount) { 
        //Security concern start here 
        if (!hasPermission(SecurityContext.getPrincipal()) { 
          throw new AccessDeniedException(); 
        } 
        //Security concern end here 
          
        //Business logic start here 
        ... 
      } 
    } 

正如您在前面的代码中看到的,应用程序有两个模块,TransferServiceAccountService。两个模块具有相同的安全性横切关注点代码。两个业务模块中加粗的代码是一样的,说明这里有代码重复。下图说明了代码分散:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

上图中有TransferServiceAccountService 和 BankService。每个业务模块都包含横切关注点,例如 SecurityLogging事务 管理。所有模块在应用程序中都有相同的关注代码。它实际上是跨应用程序的关注代码重复。

Spring AOP 为 Spring 应用程序中的代码缠结和代码分散这两个问题提供了解决方案。方面使横切关注点模块化,以避免缠结并消除分散。让我们在进一步的章节中看看 AOP 是如何解决这些问题的。

AOP 如何解决问题

Spring AOP 允许您将横切关注点逻辑与主线应用程序逻辑分开保持。这意味着,您可以实现您的主线应用程序逻辑,而只关注应用程序的核心问题。您可以编写方面来实现您的横切关注点。 Spring 提供了许多开箱即用的方面。创建方面后,您可以将这些方面(即横切行为)添加到应用程序的正确位置。让我们看下图,它说明了 AOP 的功能:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如上图所示,Security、Logging、Transaction 等各个方面在应用程序中都是单独实现的。我们在应用程序的正确位置添加了这些方面。现在我们的应用程序逻辑与关注点是分开的。让我们看下定义核心 AOP 概念的部分,并在您的应用程序中使用 AOP 的术语。

核心 AOP 术语和概念


与其他技术一样,AOP 自己的词汇表。让我们开始学习一些核心的 AOP 概念和术语。 Spring 将 AOP 范式用于 Spring AOP 模块。但不幸的是,Spring AOP 框架中使用的术语是特定于 Spring 的。这些术语用于描述 AOP 模块和特性,但并不直观。尽管如此,还是使用这些术语来理解 AOP。如果不了解 AOP 习语,您将无法理解 AOP 功能。基本上,AOP 是根据建议、切入点和连接点来定义的。让我们看下图,它说明了核心 AOP 概念以及它们在框架中是如何联系在一起的:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

在上图中,你可以看到一个 AOP 功能,它被称为 Advices,它被实现为多个点。这些点称为关节点,它们是通过使用表达式定义的。这些表达式称为 切入点。让我们使用一个例子详细了解这些术语(还记得我妻子的互联网账单故事吗?)。

建议

互联网计划用于根据互联网公司以 MB 或 GB 为单位的数据使用量来计算账单。互联网公司客户列表,他们也为他们计算互联网账单。所以计算账单并将其发送给客户是互联网公司的核心工作,而不是客户。同样,每个方面都有自己的主要工作,也有做这项工作的目的。方面的工作在 AOP 中称为建议。

正如你现在所知道的,建议是一项工作,方面将执行这项工作,所以有一些问题浮现在脑海中,何时执行这项工作以及这项工作将是什么。是否会在调用业务方法之前执行此工作?还是会在调用业务方法后执行?还是在方法调用之前和之后都执行?或者在业务方法抛出异常时执行。有时这种业务方法也称为建议方法。我们来看看Spring切面使用的following五种advises

  • Before: Advice's job executes before the advised method is invoked.

笔记

如果通知抛出异常,则不会调用目标 - 这是对 Before Advice 的有效使用。

  • After: Advice's job executes after the advised method completes regardless of whether an exception has been thrown by the target or not.
  • After-returning: Advice's job executes after the advised method successfully completes. For example, if a business method returns without throwing an exception.
  • After-throwing: Advice's job executes if the advised method exits by throwing an exception.
  • Around: This is one of the most powerful advice of Spring AOP, this advice surrounds the advised method, providing some advice's job before and after the advised method is invoked.

简而言之,advice的job code要在每个选中的点即Join Point处执行,我们再来看看AOP的另一个术语。

加入点

互联网公司为许多客户提供互联网。每个客户都有一个互联网计划,该计划需要用于他们的账单计算。在每个互联网计划的帮助下,该公司可能会为所有客户计算互联网账单。同样,您的应用程序可能有多个应用建议的位置。应用程序中的这些位置称为连接点。连接点是程序执行中的一个point,例如方法调用或抛出的异常。在这些方面,Spring 方面在您的应用程序中插入了关注功能。让我们看看 AOP 如何知道连接点并讨论 AOP 概念的另一个术语。

切入点

互联网公司根据互联网数据的使用情况制定了许多互联网计划(像我妻子这样的客户需要更多数据),因为任何互联网公司不可能为所有客户提供相同的计划或为每个客户提供独特的计划。相反,每个 plan 都分配给客户子集。同样,建议不必应用于应用程序中的所有连接点。您可以定义一个表达式来选择应用程序中的一个或多个连接点。此表达式称为 切入点。它有助于缩小方面建议的连接点。让我们看一下 AOP 的另一个术语,即 Aspect。

方面

一家互联网公司知道哪个客户 有什么互联网计划。互联网公司根据这些信息计算互联网账单并将其发送给客户。在这个例子中,互联网公司是一个方面,互联网计划是切入点,客户是加入点,公司计算互联网账单是一个建议。同样,在您的应用程序中,切面是封装切入点和建议的模块。方面知道它的作用;它在应用程序中的位置和时间。让我们看看 AOP 如何将切面应用到业务方法中。

编织

编织 是一种将方面与业务代码相结合的技术。这是通过创建新的代理对象将方面应用到目标对象的过程。编织可以在 compile 时或在类加载时或在运行时完成。 Spring AOP 通过代理模式使用运行时编织。

您已经看到了 AOP 中使用的许多术语。每当您了解任何 AOP 框架(AspectJ 或 Spring AOP)时,您都必须了解这个术语。 Spring 使用 AspectJ Framework 来实现 Spring AOP Framework。 Spring AOP 支持 AspectJ 的有限特性。 Spring AOP 提供了基于代理的 AOP 解决方案。 Spring 仅支持方法关节点。现在您对 Spring AOP 及其工作原理有了一些基本的了解,让我们继续下一个主题如何在 Spring 的声明式 AOP 模型中定义切入点。

定义切入点


如前所述,切入点用于定义将应用建议的 point。所以切入点是应用程序中一个切面最重要的元素之一。让我们了解如何定义切入点。在 Spring AOP 中,我们可以使用表达式语言来定义切入点。 Spring AOP 使用 AspectJ 的切入点表达式语言来选择应用建议的位置。 Spring AOP 支持 AspectJ 中可用的切入点指示符的子集,因为如您所知,Spring AOP 是基于代理的,并且一些指示符不支持基于代理的 AOP。让我们看看下表有 Spring AOP 支持的指示符。

Spring 支持的 AspectJ 指示符

说明

执行

它通过方法执行匹配连接点,它是 Spring AOP 支持的主要切入点指示符。

它通过限制在某些类型内匹配连接点。

这个

它将匹配限制在 bean 引用是给定类型的实例的连接点。

目标

它将匹配限制为目标对象属于给定类型的连接点。

参数

它将匹配限制为参数是给定类型的实例的连接点。

@target

它将匹配限制为目标对象具有给定类型注释的连接点。

@args

它将匹配限制在运行时,传递的实际参数的类型具有给定类型的注释的连接点。

@within

它将匹配限制在目标对象的声明类型具有给定类型注释的连接点。

@annotation

它将匹配限制为连接点的主题具有给定注释的连接点。

 

如前所述,Spring 支持切入点指示符,执行是主要的切入点指示符。所以在这里我将只向您展示如何使用执行指示符来定义切入点。让我们看看如何在应用程序中编写切入点表达式。

编写切入点

我们可以使用执行指示符来编写切入点,如下所示:

  • execution(<method pattern>): The method must match the pattern as defined follows
  • Can chain together to create composite pointcuts by using following operators: && (and), || (or), ! (not)
  • Method pattern: Following is method pattern:
    • [Modifiers] ReturnType [ClassType]
    • MethodName ([Arguments]) [throws ExceptionType]

在上述方法模式中,括号[ ]即修饰符、ClassType、参数和异常中的值都是可选值。无需使用 execution 指示符为每个切入点定义它。 ReturnTypeMethodName 等不带括号的值是强制定义的。

让我们定义一个 TransferService 接口:

    package com.packt.patterninspring.chapter6.bankapp.service; 
    public interface TransferService { 
      void transfer(String accountA, String accountB, Long amount); 
    } 

TransferService 是一种将金额从一个帐户转移到另一个帐户的服务。假设您要编写一个触发 TransferServicetransfer() 方法的日志记录方面。下图说明了一个切入点表达式,该表达式可用于在执行 transfer() 方法时应用通知:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如上图,可以看到,我使用execution()指示符来选择连接点TransferServicetransfer() 方法。在图中前面的表达式中,我在表达式的开头使用了一个星号。这意味着该方法可以返回任何类型。在星号之后,我将完全限定的类名和方法名指定为 transfer()。作为方法参数,我使用了双点(..),这意味着切入点可以选择一个名称为 transfer() 的方法,该方法没有参数或任何数量的参数。

让我们看一些更多的切入点表达式来选择连接点:

  • Any class or package:
    • execution(void transfer*(String)): Any method starting with transfer that takes a single String parameter and has a void return type
    • execution(* transfer(*)): Any method named transfer() that takes a single parameter
    • execution(* transfer(int, ..)): Any method named transfer whose first parameter is an int (the ".." signifies zero or more parameters may follow)
  • Restrict by class:
    • execution(void com.packt.patterninspring.chapter6.bankapp.service.TransferServiceImpl.*(..)): Any void method in the TransferServiceImpl class, it is including any sub-class, but will be ignored if a different implementation is used.
  • Restrict by interface:
    • execution(void com.packt.patterninspring.chapter6.bankapp.service.TransferService.transfer(*)): Any void transfer() method taking one argument, in any object implementing TransferService, it is more flexible choice--works if implementation changes.
  • Using Annotations
    • execution(@javax.annotation.security.RolesAllowed void transfer*(..)): Any void method whose name starts with transfer that is annotated with the @RolesAllowed annotation.
  • Working with packages
    • execution(* com..bankapp.*.*(..)): There is one directory between com and bankapp
    • execution(* com.*.bankapp.*.*(..)): There may be several directories between bankapp and com
    • execution(* *..bankapp.*.*(..)): Any sub-package called bankapp

现在您已经了解了编写切入点的基础知识,让我们看看如何编写建议并声明使用这些切入点的切面

创建方面


正如我之前所说,aspects 是 AOP 中最重要的术语之一。 Aspect 合并应用程序中的切入点和建议。让我们看看如何在应用程序中定义方面。

您已经将 TransferService 接口定义为方面切入点的主题。现在让我们使用 AspectJ 注释来创建一个方面。

使用注释定义方面

假设在您的银行应用程序中,您想要为汇款服务生成日志,用于审计和跟踪以了解客户的行为。如果不了解客户,企业永远不会成功。每当您从业务的角度考虑时,审计是必需的,但不是业务本身功能的核心;这是一个单独的问题。因此,将审计定义为应用于传输服务的一个方面是有意义的。让我们看看下面的代码,它显示了定义此关注点的方面的 Auditing 类:

    package com.packt.patterninspring.chapter6.bankapp.aspect; 
 
    import org.aspectj.lang.annotation.AfterReturning; 
    import org.aspectj.lang.annotation.AfterThrowing; 
    import org.aspectj.lang.annotation.Aspect; 
    import org.aspectj.lang.annotation.Before; 
 
    @Aspect 
    public class Auditing { 
 
      //Before transfer service 
      @Before("execution(* com.packt.patterninspring.chapter6.bankapp.
      service.TransferService.transfer(..))")  
      public void validate(){ 
        System.out.println("bank validate your credentials before 
        amount transferring"); 
      } 
 
      //Before transfer service 
      @Before("execution(* com.packt.patterninspring.chapter6.bankapp.
      service.TransferService.transfer(..))")  
      public void transferInstantiate(){ 
        System.out.println("bank instantiate your amount 
        transferring"); 
      } 
 
      //After transfer service 
      @AfterReturning("execution(* com.packt.patterninspring.chapter6.
      bankapp.service.TransferService.transfer(..))") 
      public void success(){ 
        System.out.println("bank successfully transferred amount"); 
      } 
 
      //After failed transfer service 
      @AfterThrowing("execution(* com.packt.patterninspring.chapter6.
      bankapp.service.TransferService.transfer(..))") 
      public void rollback() { 
        System.out.println("bank rolled back your transferred amount"); 
      } 
    } 

如您所见,Auditing 类是如何使用 @Aspect 注释进行注释的。这意味着这个类不仅仅是 Spring bean,它是应用程序的一个方面。 Auditing 类有一些方法,这些是建议并在这些方法中定义一些逻辑。正如我们所知,在开始将金额从一个帐户转移到另一个帐户之前,银行将验证(validate ())使用凭证,然后实例化(transferInstantiate()) 这个服务。验证成功后(success())金额被转账,银行审核。但是,如果在任何情况下转账失败,那么银行应该回滚(rollback ())该金额。

如您所见,Auditing 方面的所有方法都使用通知注释进行注释,以指示何时调用这些方法。 Spring AOP 提供了五种类型的通知注解来定义通知。让我们看看下表:

注释

建议

@Before

用于通知前,advice的方法在通知方法被调用之前执行。

@After

用于after通知,advice的方法在被通知的方法执行正常或异常执行后执行,无关紧要。

@AfterReturning

用于返回通知后,通知的方法在被通知的方法成功完成后执行。

@AfterThrowing

用于抛出通知后,通知的方法在方法异常终止后抛出异常后执行。

@Around

它用于围绕建议,建议的方法在被建议的方法调用之前和之后执行。

 

让我们看看建议的实现以及它们在应用程序中是如何工作的。

实施建议


如您所知,Spring提供了五种advice,我们来一一看下工作流程。

建议类型 - 之前

让我们看看下面的 figure 以获得之前的建议。这个通知在目标方法之前执行:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如图所示,在通知首先执行之前,它会调用 Target 方法。我们知道 Spring AOP 是基于代理的。所以 Proxy 对象是由目标类创建的。它基于代理设计模式和装饰器设计模式。

之前的建议示例

让我们看看 @Before 注解的使用:

    //Before transfer service 
    @Before("execution(* com.packt.patterninspring.chapter6.
    bankapp.service.TransferService.transfer(..))")  
    public void validate(){ 
      System.out.println("bank validate your credentials before amount 
      transferring"); 
    } 
 
    //Before transfer service 
    @Before("execution(* com.packt.patterninspring.chapter6.
    bankapp.service.TransferService.transfer(..))")  
    public void transferInstantiate(){ 
      System.out.println("bank instantiate your amount transferring"); 
    } 

笔记

注意——如果通知抛出异常,目标将不会被调用——这是对 Before Advice 的有效使用。

现在您已经看到了之前的建议,让我们来看看另一种类型的建议。

建议类型:返回后

让我们查看返回通知后的下图。此通知在 Target 方法成功执行后执行:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如图所示,在目标成功返回后执行返回通知。如果目标在应用程序中抛出任何异常,该建议将永远不会执行。

返回通知示例后

让我们看看 @AfterReturning 注解的使用:

    //After transfer service 
    @AfterReturning("execution(* com.packt.patterninspring.chapter6.
    bankapp.service.TransferService.transfer(..))") 
    public void success(){ 
      System.out.println("bank successfully transferred amount"); 
    } 

现在您已经看到了返回后的通知,让我们转到 Spring AOP 中的另一种类型的通知。

建议类型:投掷后

让我们查看下图后的建议。此通知在目标方法异常终止后执行。这意味着 target 方法抛出任何异常,然后这个通知将被执行。请参考下图:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如图所示,after throwing 建议是在目标抛出异常之后执行的。如果目标没有在应用程序中引发任何异常,则此建议将永远不会执行。

投掷后建议示例

让我们看看 @AfterThrowing 注解的使用:

    //After failed transfer service 
    @AfterThrowing("execution(* com.packt.patterninspring.chapter6.
    bankapp.service.TransferService.transfer(..))") 
    public void rollback() { 
      System.out.println("bank rolled back your transferred amount"); 
    } 

您还可以将 @AfterThrowing 注释与 throwing 属性一起使用,它仅在抛出正确的异常类型时才调用建议:

    //After failed transfer service 
    @AfterThrowing(value = "execution(*       
    com.packt.patterninspring.chapter6.
    bankapp.service.TransferService.transfer(..))", throwing="e")) 
    public void rollback(DataAccessException e) { 
      System.out.println("bank rolled back your transferred amount"); 
    } 

每次 TransferService 类抛出 DataAccessException 类型的异常时执行。

笔记

@AfterThrowing 建议不会阻止异常传播。但是,它可以抛出不同类型的异常。

建议类型:之后

让我们查看下图的AfterAdvice。此建议在 Target 方法正常或异常终止后执行。 Target 方法抛出任何异常或执行时没有任何异常都没有关系:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如图所示,after 通知在 target 方法通过抛出任何异常或正常终止后执行。

建议后示例

让我们看看 @After 注解的使用:

    //After transfer service 
    @After ("execution(* com.packt.patterninspring.chapter6.
    bankapp.service.TransferService.transfer(..))") 
    public void trackTransactionAttempt(){ 
      System.out.println("bank has attempted a transaction"); 
    } 

使用 @After 注解,无论目标是否抛出异常。

建议类型 - 周围

让我们看一下AroundAdvice的下图。此 advice 在调用 Target 方法之前和之后都会执行。这个建议是 Spring AOP 非常强大的建议。 Spring 框架的许多特性都是通过使用这个建议来实现的。这是 Spring 中唯一能够停止或继续目标方法执行的建议。请参考下图:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如上图所示,AroundAdvice执行了两次,第一次在advised方法之前执行,第二次在advised方法之后执行方法被调用。并且此通知还调用 proceed() 方法来在应用程序中执行建议的方法。让我们看看下面的例子:

围绕建议示例

我们来看看@Around注解的use

    @Around(execution(*    com.packt.patterninspring.chapter6.
    bankapp.service.TransferService.createCache(..))) 
    public Object cache(ProceedingJoinPoint point){ 
    Object value = cacheStore.get(CacheUtils.toKey(point)); 
    if (value == null) { 
      value = point.proceed(); 
      cacheStore.put(CacheUtils.toKey(point), value); 
    } 
    return value; 
   } 

这里我使用了 @Around 注释和一个 ProceedingJoinPoint,它继承自 Join Point 并添加了 继续() 方法。正如您在此示例中所看到的,此建议仅在 value 尚未在缓存中时才继续定位。

您已经了解了如何使用注解在应用程序中实现通知,以及如何创建方面以及如何通过注解定义切入点。在本例中,我们使用 Auditing 作为切面类,并使用 @Aspect 批注进行批注,但如果您不启用 AOP 代理行为,该批注将不起作用春天。

让我们看看下面的Java配置文件,AppConfig.java,你可以通过应用@EnableAspectJAutoProxy注解来开启自动代理班级水平:

    package com.packt.patterninspring.chapter6.bankapp.config; 
 
    import org.springframework.context.annotation.Bean; 
    import org.springframework.context.annotation.ComponentScan; 
    import org.springframework.context.annotation.Configuration; 
    import org.springframework.context.annotation.
      EnableAspectJAutoProxy; 
 
    import com.packt.patterninspring.chapter6.bankapp.aspect.Auditing; 
 
    @Configuration 
    @EnableAspectJAutoProxy 
    @ComponentScan 
    public class AppConfig { 
      @Bean 
      public Auditing auditing() { 
         return new Auditing(); 
      } 
   } 

如果您使用 XML 配置,让我们看看如何在 Spring 中连接您的 bean,以及如何使用 Spring 的 <aop:aspectj-autoproxy> 元素来启用 Spring AOP 功能。 AOP 命名空间:

    <?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/aop http://www.springframework.org/schema/aop/spring-aop.xsd 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"> 
      <context:component-scan base- package="com.packt.patterninspring.chapter6.bankapp" /> 
      <aop:aspectj-autoproxy /> 
      <bean class="com.packt.patterninspring.chapter6. bankapp.aspect.Auditing" /> 
    </beans> 

让我们看看如何在 Spring XML 配置文件中声明切面。

使用 XML 配置定义方面


正如我们所知,我们可以在基于 XML 的配置中 configure bean,类似地您可以在 XML 配置中声明方面。 Spring 提供了另一个 AOP namespace,它提供了许多用于在 XML 中声明切面的元素,让我们看看下表:

注释

并行 XML 元素

XML 元素的用途

@Before

<aop:before>

它定义了之前的建议。

@After

<aop:after>

它在建议之后定义。

@AfterReturning

<aop:after-returning>

它在返回建议后定义。

@AfterThrowing

<aop:after-throw>

它在抛出建议后定义。

@Around

<aop:around>

它围绕建议进行定义。

@Aspect

<aop:aspect>

它定义了一个方面。

@EnableAspectJAutoProxy

<aop:aspectj-autoproxy>

它使用 @AspectJ 启用注释驱动的切面。

@Pointcut

<aop:pointcut>

它定义了一个切入点。

--

<aop:advisor>

它定义了 AOP 顾问

--

<aop:config>

它是顶级 AOP 元素

正如您在上表中看到的,许多 AOP 命名空间元素与基于 Java 的配置中可用的相应注释平行。让我们在基于 XML 的配置中查看以下相同的示例,首先看一下方面类 Auditing。让我们删除所有这些 AspectJ 注释,如下面的代码所示:

    package com.packt.patterninspring.chapter6.bankapp.aspect; 
 
    public class Auditing { 
      public void validate(){ 
        System.out.println("bank validate your credentials before 
        amount transferring"); 
      } 
      public void transferInstantiate(){ 
        System.out.println("bank instantiate your amount 
        transferring"); 
      } 
      public void success(){ 
        System.out.println("bank successfully transferred amount"); 
      } 
      public void rollback() { 
        System.out.println("bank rolled back your transferred amount"); 
      } 
    } 

正如您在前面的代码中看到的,现在我们的方面类并没有表明它是一个方面类。它是一个带有一些方法的基本 Java POJO 类。让我们在下一节中看看如何在 XML 配置中声明通知:

    <aop:config> 
      <aop:aspect ref="auditing"> 
        <aop:before pointcut="execution(* com.packt.patterninspring.chapter6.bankapp. service.TransferService.transfer(..))" method="validate"/> 
        <aop:before pointcut="execution(* com.packt.patterninspring.chapter6.bankapp. service.TransferService.transfer(..))" method="transferInstantiate"/> 
        <aop:after-returning pointcut="execution(* com.packt.patterninspring.chapter6. bankapp.service.TransferService.transfer(..))" method="success"/> 
        <aop:after-throwing pointcut="execution(* com.packt.patterninspring.chapter6.bankapp. service.TransferService.transfer(..))" method="rollback"/> 
      </aop:aspect> 
    </aop:config> 

如您所见, <aop-config> 正在使用顶级元素。在 <aop:config> 中,您声明了其他元素,如 <aop:aspect>,此元素具有 ref 属性,它引用 POJO bean 审计。它表明 Auditing 是应用程序中的一个切面类。现在 <aop-aspect> 元素具有建议和切入点元素。所有逻辑都与我们在 Java 配置中定义的相同。

让我们在下一节中看看 spring 如何创建 AOP 代理。

了解 AOP 代理


如您所知,Spring AOP 是基于代理的。这意味着 Spring 创建代理来编织业务逻辑之间的切面,即在 target 对象中。它基于代理和装饰器设计模式。让我们看看 TransferServiceImpl 类作为 TransferService 接口的实现:

    package com.packt.patterninspring.chapter6.bankapp.service; 
    import org.springframework.stereotype.Service; 
    public class TransferServiceImpl implements TransferService { 
      @Override 
      public void transfer(String accountA, String accountB, Long 
      amount) { 
        System.out.println(amount+" Amount has been tranfered from 
        "+accountA+" to "+accountB); 
      } 
    } 

调用者通过对象引用直接调用这个服务(transfer()方法),我们看下图来说明更多:

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如您所见,调用者可以直接调用服务并执行分配给它的任务。

但是您将此 TransferService 声明为方面的目标。既然这样做了,事情就会发生轻微的变化。现在这个被代理和客户端代码包裹的类实际上并没有直接调用这个服务,它调用的是这个代理路由的。让我们看看下面的图表。

读书笔记《spring-5-design-patterns》第 6 章使用代理和装饰器模式的 Spring Aspect Oriented Programming

如上图所示,Spring 按以下顺序将 AOP-proxy 应用于对象:

  1. Spring creates a proxy weaving aspect and target.
  2. Proxy also implements target interface, that is, TransferServive interface.
  3. All calls for transfer service method transfer() routed through proxy interceptor.
  4. Matching advice is executed.
  5. Then target method is executed.

如前所述,是调用具有由 Spring 创建的代理的方法时的流程。

您在本章中看到了 Spring AOP 框架,它实际上使用基于代理的方面编织实现了 AspectJ 框架的某些部分。我认为,这提供了有关 Spring AOP 的良好知识。

概括


在本章中,我们看到了 Spring AOP 框架并使用了该模块背后的设计模式。 AOP 是一个非常强大的范例,它补充了面向对象的编程。 面向方面的编程AOP)模块化横切关注点例如日志、安全和事务。方面是使用 @Aspect 注释的 Java 类。它定义了一个包含横切行为的模块。该模块与应用程序的业务逻辑分离。我们可以在我们的应用程序中将它与其他业务模块一起重用,而无需进行任何更改。

在 Spring AOP 中,行为被实现为通知方法。你在Spring中学习过,有Before、AfterThrowing、AfterReturning、After和Around五种类型。环绕通知是一个非常强大的通知,使用环绕通知实现了一些有趣的功能。您已经学习了如何使用加载时间编织来编织这些建议。

您已经了解了如何在 Spring 应用程序中声明切入点,切入点选择应用建议的连接点。

现在我们将转到基本部分,看看 Spring 如何在后端连接数据库并为应用程序读取数据。从下一章开始,您将了解如何在 Spring 中使用 JDBC 模板构建应用程序。