Spring Aspect Oriented Programming with Spring

面向方面的编程(AOP)通过提供另一种思考程序结构的方式来补充面向对象的编程(OOP)。 OOP中模块化的关键单元是类,而在AOP中模块化是方面。方面支持跨多种类型和对象的关注点(例如事务管理)的模块化。 (这种关注在AOP文献中通常被称为“跨领域”关注。)

Spring的关键组件之一是AOP框架。尽管Spring IoC容器不依赖于AOP(这意味着您不需要的话就不需要使用AOP),但AOP是对Spring IoC的补充,以提供功能强大的中间件解决方案。

AOP在Spring框架中用于:

  1. 提供声明式企业服务。最重要的服务是声明式事务管理。

  2. 让用户实现自定义方面,以AOP补充其对OOP的使用。

5.1 AOP 概念

让我们首先定义一些主要的AOP概念和术语。这些术语不是特定于Spring的。不幸的是,AOP术语并不是特别直观。但是,如果使用Spring自己的术语,将会更加令人困惑。

  • Aspect: 涉及多个类别的关注点的模块化。事务管理是企业Java应用程序中横切关注的一个很好的例子。在Spring AOP中,切面是通过使用常规类(基于模式的方法)或使用@Aspect注释(@AspectJ样式)注释的常规类来实现的。
  • Join point: 程序执行过程中的一点,例如方法的执行或异常的处理。在Spring AOP中,连接点始终代表方法的执行。
  • Advice: 切面在特定的连接点处采取的操作。不同类型的建议包括“around”, “before” and “after” 通知。 包括Spring在内的许多AOP框架都将 通知建模为拦截器,并在连接点周围维护一系列拦截器。
  • Pointcut: 匹配连接点的谓词。建议与切入点表达式关联,并在与该切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。切入点表达式匹配的连接点的概念是AOP的核心,默认情况下,Spring使用AspectJ切入点表达语言。
  • Introduction: 代表类型声明其他方法或字段。 Spring AOP允许您向任何建议对象引入新接口(和相应的实现)。例如,您可以使用简介使Bean实现IsModified接口,以简化缓存。 (在AspectJ社区中,介绍被称为类型间声明。)
  • Target object: 一个或多个方面建议的对象。也称为“建议对象”。由于Spring AOP是使用运行时代理实现的,因此该对象始终是代理对象。
  • AOP proxy: 由AOP框架创建的对象,用于实施方面协定(建议方法执行等)。在Spring Framework中,AOP代理是JDK动态代理或CGLIB代理
  • Weaving: 将方面与其他应用程序类型或对象链接以创建建议的对象。这可以在编译时(例如,使用AspectJ编译器),加载时或在运行时完成。像其他纯Java AOP框架一样,Spring AOP在运行时执行编织。

Spring AOP包括以下类型的advice通知:

  • Before advice: 在连接点之前运行但无法阻止执行流前进到连接点的通知(除非它引发异常)。
  • After returning advice: 联接点正常完成后要运行的通知(例如,如果方法返回而没有引发异常)
  • After throwing advice: 如果方法因抛出异常而退出,则要运行的通知。
  • After (finally) advice: 无论连接点退出的方式如何(正常或异常返回),都应运行的通知。
  • Around advice: 围绕连接点的通知,例如方法调用。这是最有力的通知。周围通知可以在方法调用之前和之后执行自定义行为。它还负责选择是返回连接点还是通过返回其自身的返回值或引发异常来捷径通知的方法执行。

围绕通知是最通用的通知。由于Spring AOP与AspectJ一样,提供了各种通知类型,因此我们建议您使用功能最弱的通知类型,以实现所需的行为。例如,如果您只需要使用方法的返回值更新缓存,则最好使用返回后的通知而不是周围的通知,尽管周围的通知可以完成相同的事情。使用最具体的通知类型可以提供更简单的编程模型,并减少出错的可能性。例如,您不需要在用于周围通知的JoinPoint上调用proce()方法,因此,您不会失败。

所有通知参数都是静态类型的,因此您可以使用适当类型的通知参数(例如,方法执行的返回值的类型),而不是对象数组。

切入点匹配的连接点的概念是AOP的关键,它与仅提供拦截功能的旧技术有所不同。切入点使通知的目标独立于面向对象的层次结构。例如,您可以将提供声明性事务管理的环绕建议应用于跨越多个对象(例如服务层中的所有业务操作)的一组方法。

5.2 Spring AOP能力和目标

Spring AOP是用纯Java实现的。不需要特殊的编译过程。 Spring AOP不需要控制类加载器的层次结构,因此适合在Servlet容器或应用程序服务器中使用。

Spring AOP当前仅支持方法执行连接点(建议在Spring Bean上执行方法)。尽管可以在不破坏核心Spring AOP API的情况下添加对字段拦截的支持,但并未实现字段拦截。如果需要建议字段访问和更新连接点,请考虑使用诸如AspectJ之类的语言。

Spring AOP的AOP方法不同于大多数其他AOP框架。目的不是提供最完整的AOP实现(尽管Spring AOP相当强大)。相反,其目的是在AOP实现和Spring IoC之间提供紧密的集成,以帮助解决企业应用程序中的常见问题。

因此,例如,通常将Spring Framework的AOP功能与Spring IoC容器结合使用。通过使用常规bean定义语法来配置方面(尽管这允许强大的“自动代理”功能)。这是与其他AOP实现的关键区别。使用Spring AOP无法轻松或高效地完成某些事情,例如建议非常细粒度的对象(通常是域对象)。在这种情况下,AspectJ是最佳选择。但是,我们的经验是,Spring AOP为AOP可以解决的企业Java应用程序中的大多数问题提供了出色的解决方案。

Spring AOP从未努力与AspectJ竞争以提供全面的AOP解决方案。我们认为,基于代理的框架(例如Spring AOP)和成熟的框架(例如AspectJ)都是有价值的,并且它们是互补的,而不是竞争。 Spring无缝地将Spring AOP和IoC与AspectJ集成在一起,以在基于Spring的一致应用程序架构中支持AOP的所有使用。这种集成不会影响Spring AOP API或AOP Alliance API。

Spring框架的中心宗旨之一是非侵入性。这是一个想法,不应强迫您将特定于框架的类和接口引入业务或域模型。但是,在某些地方,Spring Framework确实为您提供了将特定于Spring Framework的依赖项引入代码库的选项。提供此类选项的理由是,在某些情况下,以这种方式阅读或编码某些特定功能可能会变得更加容易。但是,Spring框架(几乎)总是为您提供选择:您可以自由地就哪个选项最适合您的特定用例或场景做出明智的决定。

与本章相关的一种选择是选择哪种AOP框架(以及哪种AOP样式)。您可以选择AspectJ和/或Spring AOP。您还可以选择@AspectJ批注样式方法或Spring XML配置样式方法。本章选择首先介绍@AspectJ风格的方法这一事实不应被视为表明Spring团队更喜欢@AspectJ注释风格的方法,而不是Spring XML配置风格。

5.3 AOP Proxies

Spring AOP默认将标准JDK动态代理用于AOP代理。这使得可以代理任何接口(或一组接口)。

Spring AOP也可以使用CGLIB代理。这对于代理类而不是接口是必需的。默认情况下,如果业务对象未实现接口,则使用CGLIB。由于对接口而不是对类进行编程是一种好习惯,因此业务类通常实现一个或多个业务接口。在某些情况下(可能极少发生),当您需要建议未在接口上声明的方法或需要将代理对象作为具体类型传递给方法时,可以强制使用CGLIB。

掌握Spring AOP是基于代理的这一事实非常重要。

5.4 @AspectJ支持

@AspectJ是一种将方面声明为带有注释的常规Java类的样式。 @AspectJ样式是AspectJ项目在AspectJ 5版本中引入的。 Spring使用AspectJ提供的用于切入点解析和匹配的库来解释与AspectJ 5相同的注释。但是,AOP运行时仍然是纯Spring AOP,并且不依赖于AspectJ编译器或编织器。

5.4.1 启用@AspectJ支持

要在Spring配置中使用@AspectJ方面,您需要启用Spring支持以基于@AspectJ方面配置Spring AOP,并基于这些方面是否建议对Bean进行自动代理。通过自动代理,我们的意思是,如果Spring确定一个或多个方面建议使用bean,它将自动为该bean生成一个代理以拦截方法调用并确保按需运行建议。

可以使用XML或Java样式的配置启用@AspectJ支持。无论哪种情况,您都需要确保AspectJ的AspectJweaver.jar库位于应用程序的类路径(版本1.8或更高版本)上。该库在AspectJ发行版的lib目录中或从Maven Central存储库中可用。

  • 通过Java配置启用@AspectJ支持

    要通过Java @Configuration启用@AspectJ支持,请添加@EnableAspectJAutoProxy批注,如以下示例所示:

    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
    
    }
    
  • 通过XML配置启用@AspectJ支持

    要通过基于XML的配置启用@AspectJ支持,请使用aop:aspectj-autoproxy元素,如以下示例所示:

    <aop:aspectj-autoproxy/>
    
5.4.2 声明一个切面

启用@AspectJ支持后,Spring会自动检测在应用程序上下文中使用@AspectJ方面(具有@Aspect批注)的类定义的任何bean,并用于配置Spring AOP。接下来的两个示例显示了一个不太有用的方面所需的最小定义。

这两个示例中的第一个示例显示了应用程序上下文中的常规bean定义,该定义指向具有@Aspect批注的bean类:

<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
    <!-- configure properties of the aspect here -->
</bean>

这两个示例中的第二个示例显示了NotVeryUsefulAspect类定义,该类定义使用org.aspectj.lang.annotation.Aspect注释进行了注释;

package org.xyz;
import org.aspectj.lang.annotation.Aspect;

@Aspect
public class NotVeryUsefulAspect {

}

切面(使用@Aspect注释的类)可以具有方法和字段,与任何其他类相同。它们还可以包含切入点,通知和介绍(类型间)声明。

通过组件扫描自动检测方面

通过组件扫描自动检测方面 您可以将方面类注册为Spring XML配置中的常规bean,也可以通过类路径扫描来自动检测它们-与其他任何Spring管理的bean一样。但是,请注意,@ Aspect注释不足以在类路径中进行自动检测。为此,您需要添加一个单独的@Component批注(或者,或者,按照Spring的组件扫描器的规则,有条件的自定义构造型批注)。

向其他方面提供通知?

向其他方面提供建议? 在Spring AOP中,方面本身不能成为其他方面的建议目标。类上的@Aspect注释将其标记为一个方面,因此将其从自动代理中排除。

5.4.3 声明切入点

切入点确定了感兴趣的连接点,从而使我们能够控制运行建议的时间。 Spring AOP仅支持Spring Bean的方法执行连接点,因此您可以将切入点视为与Spring Bean上的方法执行相匹配。切入点声明由两部分组成:一个包含名称和任何参数的签名,以及一个切入点表达式,该切入点表达式精确确定我们感兴趣的方法执行。在AOP的@AspectJ批注样式中,常规方法定义提供了切入点签名。 ,并通过使用@Pointcut批注指示切入点表达式(用作切入点签名的方法必须具有void返回类型)。

一个示例可能有助于使切入点签名和切入点表达式之间的区别变得清晰。下面的示例定义一个名为anyOldTransfer的切入点,该切入点与任何名为transfer的方法的执行相匹配:

@Pointcut("execution(* transfer(..))") // the pointcut expression
private void anyOldTransfer() {} // the pointcut signature

形成@Pointcut批注的值的切入点表达式是一个常规的AspectJ 5切入点表达式。有关AspectJ的切入点语言的完整讨论,请参阅《 AspectJ编程指南》(以及扩展,包括《 AspectJ 5开发者手册》)或有关AspectJ的书籍之一(如Colyer等人的Eclipse AspectJ,或《 AspectJ in Action》 ,由Ramnivas Laddad撰写)。

  • 支持的切入点指示符

    Spring AOP支持以下在切入点表达式中使用的AspectJ切入点指示符(PCD):

    • execution: 用于匹配方法执行的连接点。这是使用Spring AOP时要使用的主要切入点指示符。
    • within: 将匹配限制为某些类型内的连接点(使用Spring AOP时,在匹配类型内声明的方法的执行)。
    • this: 限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中bean引用(Spring AOP代理)是给定类型的实例。
    • target: 限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中目标对象(正在代理的应用程序对象)是给定类型的实例。
    • args: 限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中参数是给定类型的实例。
    • @target: 限制匹配到连接点(使用Spring AOP时方法的执行)的匹配,其中执行对象的类具有给定类型的注释。
    • @args: 限制匹配的连接点(使用Spring AOP时方法的执行),其中传递的实际参数的运行时类型具有给定类型的注释。
    • @within: 限制匹配到具有给定注释的类型内的连接点(使用Spring AOP时,使用给定注释在类型中声明的方法的执行)。
    • @annotation: 将匹配点限制为连接点的主题(在Spring AOP中运行的方法)具有给定注释的连接点。

    其他切入点类型

    完整的AspectJ切入点语言支持Spring不支持的其他切入点指示符:call,get,set,preinitialization,staticinitialization,initialization,handler,adviceexecution,withincode,cflow,cflowbelow,if,@this, and@withincode。在Spring AOP解释的切入点表达式中使用这些切入点指示符会导致抛出IllegalArgumentException。 Spring AOP支持的切入点指示符集合可能会在将来的版本中扩展,以支持更多的AspectJ切入点指示符。

    由于Spring AOP仅将匹配限制为仅方法执行连接点,因此,前面对切入点指示符的讨论所给出的定义比在AspectJ编程指南中所能找到的要窄。此外,AspectJ本身具有基于类型的语义,并且在执行连接点处,此对象和目标都引用同一个对象:执行该方法的对象。 Spring AOP是一个基于代理的系统,区分代理对象本身(绑定到此对象)和代理后面的目标对象(绑定到目标)。

    由于Spring的AOP框架基于代理的性质,因此根据定义,不会拦截目标对象内的调用。对于JDK代理,只能拦截代理上的公共接口方法调用。使用CGLIB,将拦截代理上的公共方法和受保护的方法调用(必要时甚至包括程序包可见的方法)。但是,应始终通过公共签名设计通过代理进行的常见交互。

    请注意,切入点定义通常与任何拦截方法匹配。如果严格地将切入点设置为仅公开使用,即使在CGLIB代理方案中可能存在通过代理进行的非公开交互,也需要相应地进行定义。

    如果您的拦截需求包括目标类中的方法调用甚至构造函数,请考虑使用Spring驱动的本机AspectJ编织而不是Spring的基于代理的AOP框架。这构成了具有不同特性的AOP使用模式,因此请确保在做出决定之前先熟悉编织。

    Spring AOP还支持其他名为bean的PCD。使用PCD,可以将连接点的匹配限制为特定的命名Spring Bean或一组命名Spring Bean(使用通配符时)。 Bean PCD具有以下形式:

    bean(idOrNameOfBean)
    

    idOrNameOfBean令牌可以是任何Spring bean的名称。提供了使用*字符的有限通配符支持,因此,如果为Spring bean建立了一些命名约定,则可以编写bean PCD表达式来选择它们。与其他切入点指定符一样,bean PCD可以与&&(和),||一起使用。 (或),和! (否定)运算符。

    Bean PCD仅在Spring AOP中受支持,而在本机AspectJ编织中不受支持。它是AspectJ定义的标准PCD的特定于Spring的扩展,因此不适用于@Aspect模型中声明的方面。 Bean PCD在实例级别(基于Spring bean名称概念构建)上运行,而不是仅在类型级别(基于编织的AOP受其限制)上运行。基于实例的切入点指示符是Spring基于代理的AOP框架的特殊功能,并且与Spring bean工厂紧密集成,因此可以自然而直接地通过名称识别特定bean。

  • 组合切入点表达式

    您可以使用&&,||组合切入点表达式和!您也可以按名称引用切入点表达式。以下示例显示了三个切入点表达式:

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

    最佳实践是从较小的命名组件中构建更复杂的切入点表达式,如先前所示。当按名称引用切入点时,将应用常规的Java可见性规则(您可以看到相同类型的私有切入点,层次结构中受保护的切入点,任何位置的公共切入点,等等)。可见性不影响切入点匹配。

  • 共享通用切入点定义

    在使用企业应用程序时,开发人员通常希望从多个方面引用应用程序的模块和特定的操作集。我们建议为此定义一个CommonPointcuts方面,以捕获公共切入点表达式。这样的方面通常类似于以下示例:

    package com.xyz.myapp;
    
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Pointcut;
    
    @Aspect
    public class CommonPointcuts {
    
        /**
         * A join point is in the web layer if the method is defined
         * in a type in the com.xyz.myapp.web package or any sub-package
         * under that.
         */
        @Pointcut("within(com.xyz.myapp.web..*)")
        public void inWebLayer() {}
    
        /**
         * A join point is in the service layer if the method is defined
         * in a type in the com.xyz.myapp.service package or any sub-package
         * under that.
         */
        @Pointcut("within(com.xyz.myapp.service..*)")
        public void inServiceLayer() {}
    
        /**
         * A join point is in the data access layer if the method is defined
         * in a type in the com.xyz.myapp.dao package or any sub-package
         * under that.
         */
        @Pointcut("within(com.xyz.myapp.dao..*)")
        public void inDataAccessLayer() {}
    
        /**
         * A business service is the execution of any method defined on a service
         * interface. This definition assumes that interfaces are placed in the
         * "service" package, and that implementation types are in sub-packages.
         *
         * If you group service interfaces by functional area (for example,
         * in packages com.xyz.myapp.abc.service and com.xyz.myapp.def.service) then
         * the pointcut expression "execution(* com.xyz.myapp..service.*.*(..))"
         * could be used instead.
         *
         * Alternatively, you can write the expression using the 'bean'
         * PCD, like so "bean(*Service)". (This assumes that you have
         * named your Spring service beans in a consistent fashion.)
         */
        @Pointcut("execution(* com.xyz.myapp..service.*.*(..))")
        public void businessService() {}
    
        /**
         * A data access operation is the execution of any method defined on a
         * dao interface. This definition assumes that interfaces are placed in the
         * "dao" package, and that implementation types are in sub-packages.
         */
        @Pointcut("execution(* com.xyz.myapp.dao.*.*(..))")
        public void dataAccessOperation() {}
    
    }
    

    您可以在需要切入点表达式的任何地方引用在这样的方面中定义的切入点。例如,要使服务层具有事务性,您可以编写以下内容:

    <aop:config>
        <aop:advisor
            pointcut="com.xyz.myapp.CommonPointcuts.businessService()"
            advice-ref="tx-advice"/>
    </aop:config>
    
    <tx:advice id="tx-advice">
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    

    在基于模式的AOP支持中讨论了<aop:config>和<aop:advisor>元素。事务管理中讨论了事务元素。

  • 各个切入点表达式的例子

    Spring AOP用户可能最常使用执行切入点指示符。执行表达式的格式如下:

    execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern)
                    throws-pattern?)
    

    除了返回类型模式(前面的代码片段中的ret-type-pattern),名称模式和参数模式以外的所有部分都是可选的。返回类型模式确定该方法的返回类型必须是什么才能使连接点匹配。 最常用作返回类型模式。它匹配任何返回类型。仅当方法返回给定类型时,标准类型名称才匹配。名称模式与方法名称匹配。您可以将通配符用作名称模式的全部或一部分。如果您指定一个声明类型模式,请在其后加上尾随。将其加入名称模式组件。参数模式稍微复杂一些:()匹配不带参数的方法,而(…)匹配任意数量(零个或多个)的参数。 ()模式与采用任何类型的一个参数的方法匹配。 (,String)与采用两个参数的方法匹配。第一个可以是任何类型,而第二个必须是字符串。有关更多信息,请查阅AspectJ编程指南的“语言语义”部分。

    以下示例显示了一些常见的切入点表达式:

    • 任何公共方法的执行:

      execution(public * *(..))
      
    • 名称以set开头的任何方法的执行:

        execution(* set*(..))
      
    • AccountService接口定义的任何方法的执行:

      execution(* com.xyz.service.AccountService.*(..))
      
    • service 包中定义的任何方法的执行:

      execution(* com.xyz.service.*.*(..))
      
    • service 包或其子包之一中定义的任何方法的执行:

      execution(* com.xyz.service..*.*(..))
      
    • service 包中的任何连接点(仅在Spring AOP中执行方法):

       within(com.xyz.service.*)
      
    • service 包或其子包之一中的任何连接点(仅在Spring AOP中执行方法):

       within(com.xyz.service..*)
      
    • 代理实现AccountService接口的任何连接点(仅在Spring AOP中是方法执行):

      this(com.xyz.service.AccountService)
      
    • 目标对象实现AccountService接口的任何连接点(仅在Spring AOP中执行方法):

      target(com.xyz.service.AccountService)
      
    • 任何采用单个参数并且在运行时传递的参数是可序列化的连接点(仅在Spring AOP中是方法执行):

       args(java.io.Serializable)
      

      请注意,此示例中给出的切入点与execution(* *(java.io.Serializable))不同。如果在运行时传递的参数为Serializable,则args版本匹配;如果方法签名声明单个类型为Serializable的参数,则执行版本匹配。

    • 目标对象具有@Transactional批注的任何连接点(仅在Spring AOP中执行方法):

       @target(org.springframework.transaction.annotation.Transactional)
      
    • 目标对象的声明类型具有@Transactional注释的任何连接点(仅在Spring AOP中是方法执行):

      @within(org.springframework.transaction.annotation.Transactional)
      
    • 任何执行方法带有@Transactional批注的连接点(仅在Spring AOP中是方法执行):

      @annotation(org.springframework.transaction.annotation.Transactional)
      
    • 任何采用单个参数的联接点(仅在Spring AOP中是方法执行),并且传递的参数的运行时类型具有@Classified批注:

      @args(com.xyz.security.Classified)
      
    • 名为tradeService的Spring bean上的任何连接点(仅在Spring AOP中执行方法):

       bean(tradeService)
      
    • Spring Bean上具有与通配符表达式* Service匹配的名称的任何连接点(仅在Spring AOP中执行方法):

       bean(*Service)
      
  • 编写好的切入点

    在编译期间,AspectJ处理切入点以优化匹配性能。检查代码并确定每个连接点是否(静态或动态)匹配给定的切入点是一个昂贵的过程。 (动态匹配意味着无法从静态分析中完全确定匹配,并且在代码中进行测试以确定在运行代码时是否存在实际匹配)。首次遇到切入点声明时,AspectJ将其重写为匹配过程的最佳形式。这是什么意思?基本上,切入点以DNF(析取范式)重写,并且对切入点的组件进行排序,以便首先检查那些较便宜的组件。这意味着您不必担心理解各种切入点指示符的性能,并且可以在切入点声明中以任何顺序提供它们。

    但是,AspectJ只能使用所告诉的内容。为了获得最佳的匹配性能,您应该考虑他们试图实现的目标,并在定义中尽可能缩小匹配的搜索空间。现有的指示符自然会属于以下三类之一:同类,作用域和上下文:

    1. 种类的指示者选择一种特殊的连接点:execution, get, set, call, and handler.
    2. 作用域指定者选择一组感兴趣的连接点(可能是多种):withinandwithincode
    3. 上下文指示符根据上下文匹配(并可选地绑定):this,target, and@annotation

    编写正确的切入点至少应包括前两种类型(种类和作用域)。您可以包括上下文指示符以根据连接点上下文进行匹配,也可以绑定该上下文以在建议中使用。仅提供同类的标识符或仅提供上下文的标识符是可行的,但是由于额外的处理和分析,可能会影响编织性能(使用的时间和内存)。作用域指示符的匹配非常快,使用它们的使用意味着AspectJ可以非常迅速地消除不应进一步处理的连接点组。一个好的切入点应该始终包括一个切入点。

5.4.4 声明通知advice

通知与切入点表达式关联,并且在切入点匹配的方法执行之前,之后或周围运行。切入点表达式可以是对命名切入点的简单引用,也可以是就地声明的切入点表达式。

  • Before Advice

    您可以使用@Before注释在方面中声明通知。

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    @Aspect
    public class BeforeExample {
    
        @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
        public void doAccessCheck() {
            // ...
        }
    }
    

    如果使用就地切入点表达式,则可以将前面的示例重写为以下示例:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    
    @Aspect
    public class BeforeExample {
    
        @Before("execution(* com.xyz.myapp.dao.*.*(..))")
        public void doAccessCheck() {
            // ...
        }
    }
    
  • After Returning Advice

    返回建议后,当匹配的方法执行正常返回时运行建议。您可以使用@AfterReturning批注进行声明:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.AfterReturning;
    
    @Aspect
    public class AfterReturningExample {
    
        @AfterReturning("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
        public void doAccessCheck() {
            // ...
        }
    }
    

    有时,您需要在建议正文中访问返回的实际值。您可以使用@AfterReturning的形式绑定返回值以获取该访问权限,如以下示例所示:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.AfterReturning;
    
    @Aspect
    public class AfterReturningExample {
    
        @AfterReturning(
            pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
            returning="retVal")
        public void doAccessCheck(Object retVal) {
            // ...
        }
    }
    

    返回属性中使用的名称必须与advice方法中的参数名称相对应。当方法执行返回时,返回值将作为相应的参数值传递到通知方法。返回子句也将匹配限制为仅返回指定类型值的方法执行(在这种情况下为Object,它匹配任何返回值)。

  • After Throwing Advice

    抛出建议后,当匹配的方法执行通过抛出异常退出时,运行建议。您可以使用@AfterThrowing批注进行声明,如以下示例所示:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.AfterThrowing;
    
    @Aspect
    public class AfterThrowingExample {
    
        @AfterThrowing("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
        public void doRecoveryActions() {
            // ...
        }
    }
    

    通常,您希望通知仅在引发给定类型的异常时才运行,并且您通常还需要访问通知正文中的引发异常。您可以使用throwing属性来限制匹配(如果需要的话)(否则将Throwable用作异常类型),并将抛出的异常绑定到advice参数。以下示例显示了如何执行此操作:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.AfterThrowing;
    
    @Aspect
    public class AfterThrowingExample {
    
        @AfterThrowing(
            pointcut="com.xyz.myapp.CommonPointcuts.dataAccessOperation()",
            throwing="ex")
        public void doRecoveryActions(DataAccessException ex) {
            // ...
        }
    }
    

    throwing属性中使用的名称必须与advice方法中的参数名称相对应。当通过抛出异常退出方法执行时,该异常将作为相应的参数值传递给通知方法。 throwing子句还将匹配仅限制为抛出指定类型的异常(在这种情况下为DataAccessException)的那些方法执行。

    请注意,@ AfterThrowing并不表示常规的异常处理回调。具体来说,@ AfterThrowing建议方法仅应从连接点(用户声明的目标方法)本身接收异常,而不能从随附的@ After / @ AfterReturning方法接收异常。

  • After (Finally) Advice

    当匹配的方法执行退出时,建议(最终)运行。通过使用@After注释声明它。之后必须准备处理正常和异常返回条件的建议。它通常用于释放资源和类似目的。以下示例显示了最终建议后的用法:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.After;
    
    @Aspect
    public class AfterFinallyExample {
    
        @After("com.xyz.myapp.CommonPointcuts.dataAccessOperation()")
        public void doReleaseLock() {
            // ...
        }
    }
    

    请注意,AspectJ中的@After建议被定义为“ after finally建议”,类似于try-catch语句中的finally块。与@AfterReturning只适用于成功的正常返回相反,它将为从连接点(用户声明的目标方法)引发的任何结果,正常返回或异常调用。

  • Around Advice

    最后一种建议是围绕建议。围绕建议在匹配方法的执行过程中“围绕”运行。它有机会在方法运行之前和之后进行工作,并确定何时,如何以及什至实际上该方法可以运行。如果需要以线程安全的方式(例如,启动和停止计时器)在方法执行之前和之后共享状态,则通常使用绕行建议。始终使用最不符合要求的建议形式(即,在建议可以使用之前,不要在建议周围使用)。

    通过使用@Around批注来声明周围建议。咨询方法的第一个参数必须是ProceedingJoinPoint类型。在建议的正文中,在ProceedingJoinPoint上调用proce()会使底层方法运行。前进方法也可以传入Object []。数组中的值用作方法执行时的参数。

    当用Object []进行调用时,procedes的行为与AspectJ编译器所编译的aroundadvice的行为稍有不同。对于使用传统AspectJ语言编写的环绕通知,传递给proc的参数数量必须与传递给环绕通知的参数数量(而不是基础连接点采用的参数数量)相匹配,并且传递给给定的参数位置会取代该值绑定到的实体的连接点处的原始值(不要担心,如果这现在没有意义)。 Spring采取的方法更简单,并且更适合其基于代理的,仅执行的语义。仅当编译为Spring编写的@AspectJ方面并使用AspectJ编译器和weaver的参数进行处理时,才需要意识到这种区别。

    以下示例显示了如何使用周围建议:

    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.ProceedingJoinPoint;
    
    @Aspect
    public class AroundExample {
    
        @Around("com.xyz.myapp.CommonPointcuts.businessService()")
        public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
            // start stopwatch
            Object retVal = pjp.proceed();
            // stop stopwatch
            return retVal;
        }
    }
    

    周围建议返回的值是该方法的调用者看到的返回值。例如,如果一个简单的缓存方面有一个值,则它可以从缓存中返回一个值,如果没有,则调用proce()。请注意,在周围建议的正文中,proc可能被调用一次,多次或完全不被调用。所有这些都是合法的。

  • Advice Parameters

    Spring提供了完全类型化的建议,这意味着您可以在建议签名中声明所需的参数(如我们先前在返回和抛出示例中所看到的),而不是一直使用Object []数组。我们将在本节的后面部分介绍如何使参数和其他上下文值可用于建议主体。首先,我们看一下如何编写通用建议,以了解该建议当前建议的方法。

    • Access to the Current JoinPoint

      任何建议方法都可以将org.aspectj.lang.JoinPoint类型的参数声明为它的第一个参数(请注意,需要周围建议以声明ProceedingJoinPoint类型的第一个参数,该类型是JoinPoint的子类。JoinPoint接口提供了一个多种有用的方法:

      • getArgs():返回方法参数
      • getThis():返回代理对象。
      • getTarget():返回目标对象。
      • getSignature():返回所建议方法的描述。
      • toString():打印有关所建议方法的有用描述。
    • Passing Parameters to Advice

      我们已经看到了如何绑定返回的值或异常值(在返回和抛出建议后使用)。要使参数值可用于建议正文,可以使用args的绑定形式。如果在args表达式中使用参数名称代替类型名称,则在调用建议时会将相应参数的值作为参数值传递。一个例子应该使这一点更清楚。假设您想建议以Account对象作为第一个参数的DAO操作的执行,并且您需要在通知正文中访问该帐户。您可以编写以下内容:

      @Before("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
      public void validateAccount(Account account) {
          // ...
      }
      

      切入点表达式的args(account,…)部分有两个用途。首先,它将匹配限制为仅方法采用至少一个参数且传递给该参数的参数为Account实例的方法执行。其次,它通过account参数使实际的Account对象可用于建议。

      编写此代码的另一种方法是声明一个切入点,当切入点与匹配点匹配时“提供” Account对象值,然后从通知中引用命名切入点。如下所示:

      @Pointcut("com.xyz.myapp.CommonPointcuts.dataAccessOperation() && args(account,..)")
      private void accountDataAccessOperation(Account account) {}
      
      @Before("accountDataAccessOperation(account)")
      public void validateAccount(Account account) {
          // ...
      }
      

      代理对象(this),目标对象(target)和注释(@ within,@ target,@ annotation和@args)都可以以类似的方式绑定。接下来的两个示例显示如何匹配使用@Auditable注释注释的方法的执行并提取审核代码:

      这两个示例中的第一个显示了@Auditable批注的定义:

      @Retention(RetentionPolicy.RUNTIME)
      @Target(ElementType.METHOD)
      public @interface Auditable {
          AuditCode value();
      }
      

      这两个示例中的第二个示例显示了与@Auditable方法的执行相匹配的建议:

      @Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
      public void audit(Auditable auditable) {
          AuditCode code = auditable.value();
          // ...
      }
      
    • Advice Parameters and Generics

      pring AOP可以处理类声明和方法参数中使用的泛型。假设您具有如下通用类型:

      public interface Sample<T> {
          void sampleGenericMethod(T param);
          void sampleGenericCollectionMethod(Collection<T> param);
      }
      

      您可以通过在要拦截方法的参数类型中键入advice参数,将方法类型的侦听限制为某些参数类型:

      @Before("execution(* ..Sample+.sampleGenericMethod(*)) && args(param)")
      public void beforeSampleMethod(MyType param) {
          // Advice implementation
      }
      

      这种方法不适用于通用集合。因此,您不能按以下方式定义切入点:

      @Before("execution(* ..Sample+.sampleGenericCollectionMethod(*)) && args(param)")
      public void beforeSampleMethod(Collection<MyType> param) {
          // Advice implementation
      }
      

      为了使这项工作有效,我们将不得不检查集合的每个元素,这是不合理的,因为我们也无法决定通常如何处理空值。要实现类似目的,您必须将参数键入Collection <?>并手动检查元素的类型。

    • Determining Argument Names

      通知调用中的参数绑定依赖于切入点表达式中使用的名称与通知和切入点方法签名中声明的参数名称的匹配。通过Java反射无法获得参数名称,因此Spring AOP使用以下策略来确定参数名称:

      如果用户已明确指定参数名称,则使用指定的参数名称。建议和切入点注释均具有可选的argNames属性,您可以使用该属性来指定带注释的方法的参数名称。这些参数名称在运行时可用。以下示例显示如何使用argNames属性:

      @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
              argNames="bean,auditable")
      public void audit(Object bean, Auditable auditable) {
          AuditCode code = auditable.value();
          // ... use code and bean
      }
      

      如果第一个参数是JoinPoint,ProceedingJoinPoint或JoinPoint.StaticPart类型,则可以从argNames属性的值中忽略该参数的名称。例如,如果您修改前面的建议以接收连接点对象,则argNames属性不需要包括它:

      @Before(value="com.xyz.lib.Pointcuts.anyPublicMethod() && target(bean) && @annotation(auditable)",
              argNames="bean,auditable")
      public void audit(JoinPoint jp, Object bean, Auditable auditable) {
          AuditCode code = auditable.value();
          // ... use code, bean, and jp
      }
      

      对JoinPoint,ProceedingJoinPoint和JoinPoint.StaticPart类型的第一个参数给予的特殊处理对于不收集任何其他联接点上下文的建议实例特别方便。在这种情况下,您可以省略argNames属性。例如,以下建议无需声明argNames属性:

      @Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
      public void audit(JoinPoint jp) {
          // ... use jp
      }
      
      • 使用’argNames’属性有点笨拙,因此,如果未指定’argNames’属性,Spring AOP将查看该类的调试信息,并尝试从局部变量表中确定参数名称。只要已使用调试信息(至少是“ -g:vars”)编译了类,此信息就会存在。启用此标志时进行编译的结果是:(1)您的代码稍微易于理解(反向工程),(2)类文件的大小非常大(通常无关紧要),(3)删除未使用的本地代码的优化变量不适用于您的编译器。换句话说,通过启用该标志,您应该不会遇到任何困难。

        如果即使没有调试信息,AspectJ编译器(ajc)都已编译@AspectJ方面,则无需添加argNames属性,因为编译器会保留所需的信息。

      • 如果在没有必要调试信息的情况下编译了代码,Spring AOP会尝试推断绑定变量与参数的配对(例如,如果切入点表达式中仅绑定了一个变量,并且advice方法仅接受一个参数,则配对很明显)。如果在给定可用信息的情况下变量的绑定不明确,则抛出AmbiguousBindingException。

      • 如果以上所有策略均失败,则抛出IllegalArgumentException。

    • Proceeding with Arguments

      前面我们提到过,我们将描述如何编写一个在Spring AOP和AspectJ中始终有效的参数的proceed调用。解决方案是确保建议签名按顺序绑定每个方法参数。以下示例显示了如何执行此操作:

      @Around("execution(List<Account> find*(..)) && " +
              "com.xyz.myapp.CommonPointcuts.inDataAccessLayer() && " +
              "args(accountHolderNamePattern)")
      public Object preProcessQueryPattern(ProceedingJoinPoint pjp,
              String accountHolderNamePattern) throws Throwable {
          String newPattern = preProcess(accountHolderNamePattern);
          return pjp.proceed(new Object[] {newPattern});
      }
      

      在许多情况下,无论如何都要进行此绑定(如上例所示)。

  • Advice Ordering

    当多条建议都希望在同一连接点上运行时会发生什么? Spring AOP遵循与AspectJ相同的优先级规则来确定建议执行的顺序。优先级最高的建议首先“在途中”运行(因此,给定两条优先建议,则优先级最高的建议首先运行)。从连接点“出路”时,优先级最高的建议将最后运行(因此,给定两条后置通知,优先级最高的建议将第二次运行)。

    当在不同方面定义的两条建议都需要在同一连接点上运行时,除非另有说明,否则执行顺序是不确定的。您可以通过指定优先级来控制执行顺序。通过在Aspect类中实现org.springframework.core.Ordered接口或使用@Order批注对其进行注释,可以通过普通的Spring方法来完成。给定两个方面,从Ordered.getOrder()(或注释值)返回较低值的方面具有较高的优先级。

    特定方面的每种不同建议类型在概念上均应直接应用于连接点。因此,@ AfterThrowing建议方法不应从随附的@ After / @ AfterReturning方法接收异常。 从Spring Framework 5.2.7开始,在相同@Aspect类中定义的,需要在相同连接点运行的通知方法将根据其建议类型从高到低的优先级分配优先级:@Around,@Before ,@After,@AfterReturning,@AfterThrowing。但是请注意,在遵循方面的AspectJ的@After建议语义之后,在同一方面中的@AfterReturning或@AfterThrowing建议方法之后,将有效地调用@After建议方法。 当在同一个@Aspect类中定义的两个相同类型的建议(例如,两个@After建议方法)都需要在同一连接点上运行时,其顺序是不确定的(因为无法检索源)代码反射通过javac编译类的代码声明顺序)。考虑将此类建议方法折叠为每个@Aspect类中每个连接点的一个建议方法,或将建议重构为单独的@Aspect类,您可以在这些方面通过Ordered或@Order进行订购。

5.4.5 Introductions

简介(在AspectJ中称为类型间声明)使方面可以声明建议对象实现给定的接口,并代表那些对象提供该接口的实现。

您可以使用@DeclareParents批注进行介绍。此批注用于声明匹配类型具有新的父代(因此具有名称)。例如,给定一个名为UsageTracked的接口和该接口名为DefaultUsageTracked的实现,以下方面声明服务接口的所有实现者也都实现了UsageTracked接口(例如,用于通过JMX进行统计):

@Aspect
public class UsageTracking {

    @DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
    public static UsageTracked mixin;

    @Before("com.xyz.myapp.CommonPointcuts.businessService() && this(usageTracked)")
    public void recordUsage(UsageTracked usageTracked) {
        usageTracked.incrementUseCount();
    }

}

要实现的接口由带注释的字段的类型确定。 @DeclareParents批注的value属性是AspectJ类型的模式。匹配类型的任何bean均实现UsageTracked接口。请注意,在前面示例的建议中,服务Bean可以直接用作UsageTracked接口的实现。如果以编程方式访问bean,则应编写以下内容:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
5.4.6 Aspect 实例化模型

这是一个高级主题。如果您刚开始使用AOP,则可以放心地跳过它,直到以后。

默认情况下,应用程序上下文中每个方面都有一个实例。 AspectJ将此称为单例实例化模型。可以使用备用生命周期来定义方面。 Spring支持AspectJ的perthis和pertarget实例化模型;目前不支持percflow,percflowbelow和pertypewithin。 您可以通过在@Aspect批注中指定perthis子句来声明perthis方面。考虑以下示例:

@Aspect("perthis(com.xyz.myapp.CommonPointcuts.businessService())")
public class MyAspect {

    private int someState;

    @Before("com.xyz.myapp.CommonPointcuts.businessService()")
    public void recordServiceUsage() {
        // ...
    }
}

在前面的示例中,perthis子句的作用是为执行业务服务的每个唯一服务对象(每个与切入点表达式匹配的连接点绑定到该唯一对象)创建一个方面实例。方面实例是在服务对象上首次调用方法时创建的。当服务对象超出范围时,方面将超出范围。在创建方面实例之前,其中的任何建议都不会运行。创建方面实例后,在其中声明的建议将在匹配的连接点处运行,但是仅当服务对象是与此方面相关联的对象时才运行。有关每个子句的更多信息,请参见AspectJ编程指南。 pertarget实例化模型的工作方式与perthis完全相同,但是它在匹配的连接点为每个唯一目标对象创建一个方面实例。

5.4.7 An AOP Example

既然您已经了解了所有组成部分是如何工作的,那么我们可以将它们放在一起做一些有用的事情。

有时由于并发问题(例如,死锁失败者),业务服务的执行可能会失败。如果重试该操作,则很可能在下一次尝试中成功。对于适合在这种情况下重试的业务(不需要为解决冲突而需要返回给用户的幂等操作),我们希望透明地重试该操作,以避免客户端看到PessimisticLockingFailureException。这项要求清楚地跨越了服务层中的多个服务,因此非常适合通过一个方面实施。

因为我们想重试该操作,所以我们需要使用围绕建议,以便可以多次调用proced。以下清单显示了基本方面的实现:

@Aspect
public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    @Around("com.xyz.myapp.CommonPointcuts.businessService()")
    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}

请注意,方面实现了Ordered接口,因此我们可以将方面的优先级设置为高于事务建议(每次重试时都希望有新的事务)。 maxRetries和order属性均由Spring配置。建议的主要动作发生在doConcurrentOperation中。注意,目前,我们将重试逻辑应用于每个businessService()。我们尝试继续,如果失败并出现PessimisticLockingFailureException,则我们将再次尝试,除非我们用尽了所有重试尝试。

相应的Spring配置如下:

<aop:aspectj-autoproxy/>

<bean id="concurrentOperationExecutor" class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
    <property name="maxRetries" value="3"/>
    <property name="order" value="100"/>
</bean>

为了完善方面,使其仅重试幂等运算,我们可以定义以下幂等注解:

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}

然后,我们可以使用注释来注释服务操作的实现。方面的更改仅重试幂等操作涉及到修改切入点表达式,以便仅@Idempotent操作匹配,如下所示:

@Around("com.xyz.myapp.CommonPointcuts.businessService() && " +
        "@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
    // ...
}

5.5 基于XML格式的AOP支持

如果您喜欢基于XML的格式,Spring还提供了对使用aop名称空间标签定义方面的支持。支持与使用@AspectJ样式时完全相同的切入点表达式和建议类型。因此,在本节中,我们将重点放在该语法上,并使读者参考上一节的讨论(@AspectJ支持),以了解编写切入点表达式和建议参数的绑定。

在您的Spring配置中,所有pointcut, advisor 程序元素都必须放在<aop:config>元素内(在应用程序上下文配置中可以有多个<aop:config>元素)。 <aop:config>元素可以包含pointcut, advisor, and aspect 元素(请注意,必须按此顺序声明它们)。

<aop:config>的配置风格大量使用了Spring的自动代理机制。如果您已经通过使用BeanNameAutoProxyCreator或类似方法使用显式自动代理,则可能会导致问题(例如,未编制建议)。推荐的使用模式是仅使用<aop:config>样式或仅使用AutoProxyCreator样式,并且不要混合使用。

5.5.1 Declaring an Aspect

使用模式支持时,方面是在Spring应用程序上下文中定义为Bean的常规Java对象。状态和行为在对象的字段和方法中捕获,切入点和建议信息在XML中捕获。

您可以使用<aop:aspect>元素声明一个方面,并使用ref属性引用该支持bean,如以下示例所示:

<aop:config>
    <aop:aspect id="myAspect" ref="aBean">
        ...
    </aop:aspect>
</aop:config>

<bean id="aBean" class="...">
    ...
</bean>

支持方面的bean(在本例中为aBean)当然可以像配置任何其他Spring bean一样进行配置并注入依赖项。

5.5.2 Declaring a Pointcut

您可以在<aop:config>元素内声明一个命名的切入点,让切入点定义在多个方面和顾问程序之间共享。

可以定义代表服务层中任何业务服务的执行的切入点:

<aop:config>

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

</aop:config>

请注意,切入点表达式本身使用的是@AspectJ支持中所述的AspectJ切入点表达式语言。如果使用基于架构的声明样式,则可以引用在切入点表达式中的类型(@Aspects)中定义的命名切入点。定义上述切入点的另一种方法如下:

<aop:config>

    <aop:pointcut id="businessService"
        expression="com.xyz.myapp.CommonPointcuts.businessService()"/>

</aop:config>

假定您具有共享公共切入点定义中所述的CommonPointcuts方面。

然后,在方面内声明切入点与声明顶级切入点非常相似,如以下示例所示:

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

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

        ...
    </aop:aspect>

</aop:config>

与@AspectJ方面几乎相同,使用基于架构的定义样式声明的切入点可以收集连接点上下文。例如,以下切入点收集此对象作为连接点上下文,并将其传递给建议:

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) &amp;&amp; this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>

</aop:config>

必须声明通知,以通过包含匹配名称的参数来接收收集的连接点上下文,如下所示:

public void monitor(Object service) {
    // ...
}

组合切入点子表达式时,&&在XML文档中很尴尬,因此您可以分别使用and,or或not关键字代替&&,||和!。例如,上一个切入点可以更好地编写如下:

<aop:config>

    <aop:aspect id="myAspect" ref="aBean">

        <aop:pointcut id="businessService"
            expression="execution(* com.xyz.myapp.service.*.*(..)) and this(service)"/>

        <aop:before pointcut-ref="businessService" method="monitor"/>

        ...
    </aop:aspect>
</aop:config>

请注意,以这种方式定义的切入点由其XML ID引用,并且不能用作命名切入点以形成复合切入点。因此,基于架构的定义样式中的命名切入点支持比@AspectJ样式所提供的更受限制。

5.5.3 Declaring Advice

基于模式的AOP支持使用与@AspectJ样式相同的五种建议,并且它们具有完全相同的语义。

  • Before Advice

    在执行匹配的方法之前,先运行建议。使用<aop:before>元素在<aop:aspect>中声明它,如以下示例所示:

    <aop:aspect id="beforeExample" ref="aBean">
    
        <aop:before
            pointcut-ref="dataAccessOperation"
            method="doAccessCheck"/>
    
        ...
    
    </aop:aspect>
    

    在这里,dataAccessOperation是在最高(<aop:config>)级别定义的切入点的ID。要定义内联切入点,请使用以下方法将pointcut-ref属性替换为pointcut属性:

    <aop:aspect id="beforeExample" ref="aBean">
    
        <aop:before
            pointcut="execution(* com.xyz.myapp.dao.*.*(..))"
            method="doAccessCheck"/>
    
        ...
    </aop:aspect>
    

    正如我们在@AspectJ样式的讨论中所指出的那样,使用命名的切入点可以显着提高代码的可读性。 method属性标识提供建议正文的方法(doAccessCheck)。必须为包含建议的Aspect元素所引用的bean定义此方法。在执行数据访问操作(与切入点表达式匹配的方法执行连接点)之前,将调用Aspect Bean上的doAccessCheck方法。

  • After Returning Advice

    返回的建议在匹配的方法执行正常完成时运行。它在<aop:aspect>内以与之前建议相同的方式声明。以下示例显示了如何声明它:

    <aop:aspect id="afterReturningExample" ref="aBean">
    
        <aop:after-returning
            pointcut-ref="dataAccessOperation"
            method="doAccessCheck"/>
    
        ...
    </aop:aspect>
    

    与@AspectJ样式一样,您可以在建议正文中获取返回值。为此,使用returning属性指定返回值应传递到的参数的名称,如以下示例所示:

    <aop:aspect id="afterReturningExample" ref="aBean">
    
        <aop:after-returning
            pointcut-ref="dataAccessOperation"
            returning="retVal"
            method="doAccessCheck"/>
    
        ...
    </aop:aspect>
    

    doAccessCheck方法必须声明一个名为retVal的参数。该参数的类型以与@AfterReturning中所述相同的方式约束匹配。例如,您可以按以下方式声明方法签名:

    public void doAccessCheck(Object retVal) {...
    
  • After Throwing Advice

    抛出建议后,当匹配的方法执行通过抛出异常退出时,运行建议。通过使用掷后元素在<aop:aspect>中声明它,如以下示例所示:

    <aop:aspect id="afterThrowingExample" ref="aBean">
    
        <aop:after-throwing
            pointcut-ref="dataAccessOperation"
            method="doRecoveryActions"/>
    
        ...
    </aop:aspect>
    

    与@AspectJ样式一样,您可以在通知正文中获取引发的异常。为此,请使用throwing属性指定异常应传递到的参数的名称,如以下示例所示:

    <aop:aspect id="afterThrowingExample" ref="aBean">
    
        <aop:after-throwing
            pointcut-ref="dataAccessOperation"
            throwing="dataAccessEx"
            method="doRecoveryActions"/>
    
        ...
    </aop:aspect>
    

    doRecoveryActions方法必须声明一个名为dataAccessEx的参数。此参数的类型以与@AfterThrowing中所述相同的方式约束匹配。例如,方法签名可以声明如下:

    public void doRecoveryActions(DataAccessException dataAccessEx) {...
    
  • After (Finally) Advice

    无论最终如何执行匹配的方法,建议(最终)都会运行。您可以使用after元素声明它,如以下示例所示:

    <aop:aspect id="afterFinallyExample" ref="aBean">
    
        <aop:after
            pointcut-ref="dataAccessOperation"
            method="doReleaseLock"/>
    
        ...
    </aop:aspect>
    
  • Around Advice

    最后一种建议是围绕建议。围绕建议在匹配的方法执行过程中“围绕”运行。它有机会在方法运行之前和之后进行工作,并确定何时,如何以及什至实际上该方法可以运行。周围建议通常用于以线程安全的方式(例如,启动和停止计时器)在方法执行之前和之后共享状态。始终使用最不强大的建议形式,以满足您的要求。如果建议可以完成这项工作,请不要在建议周围使用。

    您可以使用aop:around元素声明周围建议。咨询方法的第一个参数必须是ProceedingJoinPoint类型。在建议的正文中,在ProceedingJoinPoint上调用proce()会使底层方法运行。还可以使用Object []调用proce方法。数组中的值用作方法执行时的参数。有关调用Object []的注意事项,请参见“周围建议”。以下示例显示了如何在XML中围绕建议进行声明:

    <aop:aspect id="aroundExample" ref="aBean">
    
        <aop:around
            pointcut-ref="businessService"
            method="doBasicProfiling"/>
    
        ...
    </aop:aspect>
    

    doBasicProfiling建议的实现可以与@AspectJ示例完全相同(当然要减去注释),如以下示例所示:

    public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
        // start stopwatch
        Object retVal = pjp.proceed();
        // stop stopwatch
        return retVal;
    }
    
  • Advice Parameters

    基于架构的声明样式以与@AspectJ支持相同的方式支持完全类型的建议,即通过名称与建议方法参数匹配切入点参数。有关详细信息,请参见建议参数。如果您希望显式指定建议方法的参数名称(不依赖于先前描述的检测策略),则可以通过使用advice元素的arg-names属性来实现,该属性的处理方式与argNames属性相同在建议注释中(如确定参数名称中所述)。以下示例显示如何在XML中指定参数名称:

    <aop:before
        pointcut="com.xyz.lib.Pointcuts.anyPublicMethod() and @annotation(auditable)"
        method="audit"
        arg-names="auditable"/>
    

    arg-names属性接受以逗号分隔的参数名称列表。

    以下基于XSD的方法中涉及程度稍高的示例显示了一些与一些强类型参数结合使用的建议:

    package x.y.service;
    
    public interface PersonService {
    
        Person getPerson(String personName, int age);
    }
    
    public class DefaultPersonService implements PersonService {
    
        public Person getPerson(String name, int age) {
            return new Person(name, age);
        }
    }
    

    接下来是方面。请注意profile(…)方法接受许多强类型参数的事实,其中第一个恰好是用于进行方法调用的连接点。此参数的存在表明profile(…)将用作建议,如以下示例所示:

    package x.y;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.springframework.util.StopWatch;
    
    public class SimpleProfiler {
    
        public Object profile(ProceedingJoinPoint call, String name, int age) throws Throwable {
            StopWatch clock = new StopWatch("Profiling for '" + name + "' and '" + age + "'");
            try {
                clock.start(call.toShortString());
                return call.proceed();
            } finally {
                clock.stop();
                System.out.println(clock.prettyPrint());
            }
        }
    }
    

    最后,以下示例XML配置影响了特定连接点的上述建议的执行:

    <beans xmlns="http://www.springframework.org/schema/beans"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xsi:schemaLocation="
            http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- this is the object that will be proxied by Spring's AOP infrastructure -->
        <bean id="personService" class="x.y.service.DefaultPersonService"/>
    
        <!-- this is the actual advice itself -->
        <bean id="profiler" class="x.y.SimpleProfiler"/>
    
        <aop:config>
            <aop:aspect ref="profiler">
    
                <aop:pointcut id="theExecutionOfSomePersonServiceMethod"
                    expression="execution(* x.y.service.PersonService.getPerson(String,int))
                    and args(name, age)"/>
    
                <aop:around pointcut-ref="theExecutionOfSomePersonServiceMethod"
                    method="profile"/>
    
            </aop:aspect>
        </aop:config>
    
    </beans>
    

    考虑以下驱动程序脚本:

    import org.springframework.beans.factory.BeanFactory;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import x.y.service.PersonService;
    
    public final class Boot {
    
        public static void main(final String[] args) throws Exception {
            BeanFactory ctx = new ClassPathXmlApplicationContext("x/y/plain.xml");
            PersonService person = (PersonService) ctx.getBean("personService");
            person.getPerson("Pengo", 12);
        }
    }
    

    有了这样的Boot类,我们将在标准输出上获得类似于以下内容的输出:

    StopWatch 'Profiling for 'Pengo' and '12'': running time (millis) = 0
    -----------------------------------------
    ms     %     Task name
    -----------------------------------------
    00000  ?  execution(getFoo)
    
  • Advice Ordering

    当需要在同一连接点(执行方法)上运行多个建议时,排序规则如“建议排序”中所述。方面之间的优先级是通过<aop:aspect>元素中的order属性或通过将@Order批注添加到支持方面的bean或通过使bean实现Ordered接口来确定的。

    与在同一@Aspect类中定义的通知方法的优先规则相反,当在同一<aop:aspect>元素中定义的两条建议都需要在同一连接点上运行时,优先级由中的顺序确定在封闭的<aop:aspect>元素中声明的通知元素,从最高优先级到最低优先级。 例如,给定一个绕行建议和一个在同一<aop:aspect>元素中定义的,适用于同一联接点的事前通知,以确保绕行建议的优先级高于事前通知的<aop:around>元素必须在<aop:before>元素之前声明。 根据一般经验,如果发现在同一<aop:aspect>元素中定义了多个建议,这些建议适用于同一连接点,请考虑将这些建议方法折叠为每个< aop:aspect>元素,或将建议重构为单独的<aop:aspect>元素,您可以在方面级别进行订购。

5.5.4 Introductions

简介(在AspectJ中称为类型间声明)使方面可以声明建议的对象实现给定的接口,并代表那些对象提供该接口的实现。

您可以通过在aop:aspect中使用aop:declare-parents元素进行介绍。您可以使用aop:declare-parents元素声明匹配类型具有新的父代(因此而得名)。例如,给定一个名为UsageTracked的接口和该接口名为DefaultUsageTracked的实现,以下方面声明服务接口的所有实现者也都实现UsageTracked接口。 (例如,为了通过JMX公开统计信息。)

<aop:aspect id="usageTrackerAspect" ref="usageTracking">

    <aop:declare-parents
        types-matching="com.xzy.myapp.service.*+"
        implement-interface="com.xyz.myapp.service.tracking.UsageTracked"
        default-impl="com.xyz.myapp.service.tracking.DefaultUsageTracked"/>

    <aop:before
        pointcut="com.xyz.myapp.CommonPointcuts.businessService()
            and this(usageTracked)"
            method="recordUsage"/>

</aop:aspect>

支持usageTracking bean的类将包含以下方法:

public void recordUsage(UsageTracked usageTracked) {
    usageTracked.incrementUseCount();
}

要实现的接口由Implement-interface属性确定。类型匹配属性的值是AspectJ类型模式。匹配类型的任何bean均实现UsageTracked接口。请注意,在前面示例的建议中,服务Bean可以直接用作UsageTracked接口的实现。要以编程方式访问bean,可以编写以下代码:

UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
5.5.5. Aspect Instantiation Models

模式定义方面唯一受支持的实例化模型是单例模型。将来的版本中可能会支持其他实例化模型。

5.5.6. Advisors

“顾问”的概念来自Spring中定义的AOP支持,并且在AspectJ中没有直接等效的概念。顾问就像一个独立的小方面,只有一条建议。通知本身由Bean表示,并且必须实现Spring的“建议类型”中描述的建议接口之一。顾问可以利用AspectJ切入点表达式。

Spring通过<aop:advisor>元素支持顾问程序概念。您通常会看到它与事务建议结合使用,事务建议在Spring中也有自己的名称空间支持。以下示例显示顾问程序:

<aop:config>

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

    <aop:advisor
        pointcut-ref="businessService"
        advice-ref="tx-advice"/>

</aop:config>

<tx:advice id="tx-advice">
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

除了前面的示例中使用的pointcut-ref属性,您还可以使用pointcut属性内联定义一个pointcut表达式。 要定义顾问程序的优先级以使建议书可以参与订购,请使用order属性定义顾问程序的Ordered值。

5.5.7. An AOP Schema Example

本节说明使用模式支持重写时,AOP示例中的并发锁定失败重试示例的外观。

有时由于并发问题(例如,死锁失败者),业务服务的执行可能会失败。如果重试该操作,则很可能在下一次尝试中成功。对于适合在这种情况下重试的业务(不需要为解决冲突而需要返回给用户的幂等操作),我们希望透明地重试该操作,以避免客户端看到PessimisticLockingFailureException。这项要求清楚地跨越了服务层中的多个服务,因此非常适合通过一个方面实施。 因为我们想重试该操作,所以我们需要使用围绕建议,以便可以多次调用proced。以下清单显示了基本方面的实现(这是使用模式支持的常规Java类):

public class ConcurrentOperationExecutor implements Ordered {

    private static final int DEFAULT_MAX_RETRIES = 2;

    private int maxRetries = DEFAULT_MAX_RETRIES;
    private int order = 1;

    public void setMaxRetries(int maxRetries) {
        this.maxRetries = maxRetries;
    }

    public int getOrder() {
        return this.order;
    }

    public void setOrder(int order) {
        this.order = order;
    }

    public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
        int numAttempts = 0;
        PessimisticLockingFailureException lockFailureException;
        do {
            numAttempts++;
            try {
                return pjp.proceed();
            }
            catch(PessimisticLockingFailureException ex) {
                lockFailureException = ex;
            }
        } while(numAttempts <= this.maxRetries);
        throw lockFailureException;
    }
}

请注意,方面实现了Ordered接口,因此我们可以将方面的优先级设置为高于事务建议(每次重试时都希望有新的事务)。 maxRetries和order属性均由Spring配置。主要操作发生在建议方法周围的doConcurrentOperation中。我们尝试继续。如果由于PessimisticLockingFailureException失败,则将重试,除非我们用尽了所有重试尝试。

相应的Spring配置如下:

<aop:config>

    <aop:aspect id="concurrentOperationRetry" ref="concurrentOperationExecutor">

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

        <aop:around
            pointcut-ref="idempotentOperation"
            method="doConcurrentOperation"/>

    </aop:aspect>

</aop:config>

<bean id="concurrentOperationExecutor"
    class="com.xyz.myapp.service.impl.ConcurrentOperationExecutor">
        <property name="maxRetries" value="3"/>
        <property name="order" value="100"/>
</bean>

请注意,目前我们假设所有业务服务都是幂等的。如果不是这种情况,我们可以改进方面,以便通过引入幂等注释并使用该注释来注释服务操作的实现,使其仅重试真正的幂等操作,如以下示例所示:

@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
    // marker annotation
}

方面的更改仅重试幂等操作涉及到修改切入点表达式,以便仅@Idempotent操作匹配,如下所示:

<aop:pointcut id="idempotentOperation"
        expression="execution(* com.xyz.myapp.service.*.*(..)) and
        @annotation(com.xyz.myapp.service.Idempotent)"/>

5.6 选择要使用的AOP声明样式

一旦确定方面是实现给定需求的最佳方法,您如何在使用Spring AOP或AspectJ以及在Aspect语言(代码)样式,@ AspectJ批注样式或Spring XML样式之间做出选择?这些决定受许多因素影响,包括应用程序需求,开发工具和团队对AOP的熟悉程度。

5.6.1 Spring AOP or Full AspectJ?

使用最简单的方法即可。 Spring AOP比使用完整的AspectJ更简单,因为不需要在开发和构建过程中引入AspectJ编译器/编织器。如果您只需要建议在Spring bean上执行操作,则Spring AOP是正确的选择。如果您需要建议不受Spring容器管理的对象(通常是域对象),则需要使用AspectJ。如果您希望建议除简单方法执行以外的连接点(例如,字段get或设置连接点等),则还需要使用AspectJ。

使用AspectJ时,可以选择AspectJ语言语法(也称为“代码样式”)或@AspectJ注释样式。显然,如果您不使用Java 5+,则可以为您做出选择:使用代码样式。如果方面在您的设计中起着重要的作用,并且您能够将AspectJ开发工具(AJDT)插件用于Eclipse,则AspectJ语言语法是首选。它更干净,更简单,因为该语言是专为编写方面而设计的。如果您不使用Eclipse或只有少数几个方面在您的应用程序中不起作用,那么您可能要考虑使用@AspectJ样式,在IDE中坚持常规Java编译,并向其中添加方面编织阶段您的构建脚本。

5.6.2 @AspectJ or XML for Spring AOP?

如果选择使用Spring AOP,则可以选择@AspectJ或XML样式。有各种折衷考虑。

XML样式可能是现有Spring用户最熟悉的,并且得到了真正的POJO的支持。将AOP用作配置企业服务的工具时,XML可能是一个不错的选择(一个很好的测试是您是否将切入点表达式视为配置的一部分,而您可能希望独立更改)。使用XML样式,可以说从您的配置中可以更清楚地了解系统中存在哪些方面。

XML样式有两个缺点。首先,它没有完全将要解决的需求的实现封装在一个地方。 DRY原则说,系统中的任何知识都应该有一个单一,明确,权威的表示形式。使用XML样式时,关于如何实现需求的知识会在配置文件中的后备bean类的声明和XML中分散。当您使用@AspectJ样式时,此信息将封装在一个模块中:方面。其次,与@AspectJ样式相比,XML样式在表达能力上有更多限制:仅支持“单例”方面实例化模型,并且无法组合以XML声明的命名切入点。例如,使用@AspectJ样式,您可以编写如下内容:

@Pointcut("execution(* get*())")
public void propertyAccess() {}

@Pointcut("execution(org.xyz.Account+ *(..))")
public void operationReturningAnAccount() {}

@Pointcut("propertyAccess() && operationReturningAnAccount()")
public void accountPropertyAccess() {}

在XML样式中,您可以声明前两个切入点:

<aop:pointcut id="propertyAccess"
        expression="execution(* get*())"/>

<aop:pointcut id="operationReturningAnAccount"
        expression="execution(org.xyz.Account+ *(..))"/>

XML方法的缺点是您无法通过组合这些定义来定义accountPropertyAccess切入点。

@AspectJ样式支持其他实例化模型和更丰富的切入点组合。它具有将方面保持为模块化单元的优势。它还具有的优点是,Spring AOP和AspectJ都可以理解@AspectJ方面。因此,如果您以后决定需要AspectJ的功能来实现其他要求,则可以轻松地迁移到经典的AspectJ设置。总而言之,Spring团队在自定义方面更喜欢@AspectJ样式,而不是简单地配置企业服务。

5.7 混合切面类型

通过使用自动代理支持,模式定义的<aop:aspect>方面,<aop:advisor>声明的顾问程序,甚至是同一配置中其他样式的代理和拦截器,完全可以混合@AspectJ样式的方面。所有这些都是通过使用相同的基础支持机制实现的,并且可以毫无困难地共存。

5.8 代理机制

Spring AOP使用JDK动态代理或CGLIB创建给定目标对象的代理。 JDK内置了JDK动态代理,而CGLIB是常见的开源类定义库(重新包装到spring-core中)。

如果要代理的目标对象实现至少一个接口,则使用JDK动态代理。代理了由目标类型实现的所有接口。如果目标对象未实现任何接口,则将创建CGLIB代理。

如果要强制使用CGLIB代理(例如,代理为目标对象定义的每个方法,而不仅是由其接口实现的方法),都可以这样做。但是,您应该考虑以下问题:

  • 使用CGLIB,不能advised final方法,因为不能在运行时生成的子类中覆盖它们。
  • 从Spring 4.0开始,由于CGLIB代理实例是通过Objenesis创建的,因此不再调用代理对象的构造函数两次。仅当您的JVM不允许绕过构造函数时,您才可能从Spring的AOP支持中看到两次调用和相应的调试日志条目。

要强制使用CGLIB代理,请将<aop:config>元素的proxy-target-class属性的值设置为true,如下所示:

<aop:config proxy-target-class="true">
    <!-- other beans defined here... -->
</aop:config>

要在使用@AspectJ自动代理支持时强制CGLIB代理,请将<aop:aspectj-autoproxy>元素的proxy-target-class属性设置为true,如下所示:

<aop:aspectj-autoproxy proxy-target-class="true"/>

多个<aop:config />部分在运行时折叠到一个统一的自动代理创建器中,该创建器将应用任何<aop:config />部分(通常来自不同XML Bean定义文件)指定的最强代理设置。这也适用于<tx:annotation-driven />和<aop:aspectj-autoproxy />元素。 明确地说,在<tx:annotation-driven />,<aop:aspectj-autoproxy />或<aop:config />元素上使用proxy-target-class =“ true”会强制对所有三个元素使用CGLIB代理其中。

5.8.1 了解AOP代理

Spring AOP是基于代理的。在编写自己的方面或使用Spring框架随附的任何基于Spring AOP的方面之前,掌握最后一条语句实际含义的语义至关重要。

首先考虑以下情形:您有一个普通的,未经代理的,没有什么特别的,直接的对象引用,如以下代码片段所示:

public class SimplePojo implements Pojo {

    public void foo() {
        // this next method invocation is a direct call on the 'this' reference
        this.bar();
    }

    public void bar() {
        // some logic...
    }
}

如果在对象引用上调用方法,则直接在该对象引用上调用该方法,如下图和清单所示:

在这里插入图片描述

public class Main {

    public static void main(String[] args) {
        Pojo pojo = new SimplePojo();
        // this is a direct method call on the 'pojo' reference
        pojo.foo();
    }
}

当客户端代码具有的引用是代理时,情况会稍有变化。考虑以下图表和代码片段:

在这里插入图片描述

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

这里要理解的关键是Main类的main(…)方法中的客户端代码具有对代理的引用。这意味着该对象引用上的方法调用是代理上的调用。结果,代理可以委派给与该特定方法调用相关的所有拦截器(建议)。但是,一旦调用最终到达目标对象(在本例中为SimplePojo引用),则可能会针对它调用可能对其自身进行的任何方法调用,例如this.bar()或this.foo()。此参考,而不是代理。这具有重要意义。这意味着自调用不会导致与方法调用相关的建议得到运行的机会。

好吧,那该怎么办?最佳方法(此处宽松地使用术语“最佳”)是重构代码,以免发生自调用。这确实需要您做一些工作,但这是最好的,侵入性最小的方法。下一种方法绝对可怕,我们正要指出这一点,恰恰是因为它是如此可怕。您可以(对我们来说是痛苦的)完全将类中的逻辑与Spring AOP绑定在一起,如以下示例所示:

public class SimplePojo implements Pojo {

    public void foo() {
        // this works, but... gah!
        ((Pojo) AopContext.currentProxy()).bar();
    }

    public void bar() {
        // some logic...
    }
}

这将您的代码完全耦合到Spring AOP,并且使类本身意识到在AOP上下文中使用它的事实,而AOP上下文却是这样。创建代理时,还需要一些其他配置,如以下示例所示:

public class Main {

    public static void main(String[] args) {
        ProxyFactory factory = new ProxyFactory(new SimplePojo());
        factory.addInterface(Pojo.class);
        factory.addAdvice(new RetryAdvice());
        factory.setExposeProxy(true);

        Pojo pojo = (Pojo) factory.getProxy();
        // this is a method call on the proxy!
        pojo.foo();
    }
}

最后,必须注意,AspectJ没有此自调用问题,因为它不是基于代理的AOP框架。

5.9 以编程方式创建@AspectJ代理

除了使用<aop:config>或<aop:aspectj-autoproxy>声明配置中的各个方面外,还可以通过编程方式创建建议目标对象的代理。在这里,我们要重点介绍通过使用@AspectJ方面自动创建代理的功能。

您可以使用org.springframework.aop.aspectj.annotation.AspectJProxyFactory类为一个或多个@AspectJ方面建议的目标对象创建代理。此类的基本用法非常简单,如以下示例所示:

// create a factory that can generate a proxy for the given target object
AspectJProxyFactory factory = new AspectJProxyFactory(targetObject);

// add an aspect, the class must be an @AspectJ aspect
// you can call this as many times as you need with different aspects
factory.addAspect(SecurityManager.class);

// you can also add existing aspect instances, the type of the object supplied must be an @AspectJ aspect
factory.addAspect(usageTracker);

// now get the proxy object...
MyInterfaceType proxy = factory.getProxy();

5.10 在Spring应用程序中使用AspectJ

​ 参考附录参考文献中的使用介绍

5.11 更多资源

可以在AspectJ网站上找到有关AspectJ的更多信息。 Eclipse AspectJ,作者:Adrian Colyer等。等(Addison-Wesley,2005年)为AspectJ语言提供了全面的介绍和参考。 强烈推荐Ramnivas Laddad撰写的《 AspectJ in Action》第二版(Manning,2009年)。本书的重点是AspectJ,但在一定程度上探讨了许多通用的AOP主题。

参考文献

【https://docs.spring.io/spring-framework/docs/current/reference/html/core.html】【5. Aspect Oriented Programming with Spring】

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值