接上一篇博客:https://blog.csdn.net/qq_43605444/article/details/122050740?spm=1001.2014.3001.5502

6、选择要使用的 AOP 声明样式

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

6.1 Spring AOP 还是 Full AspectJ?

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

当您使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为“代码风格”)或 @AspectJ 注释风格。 显然,如果您不使用 Java 5+,那么已经为您做出了选择:使用代码风格。 如果方面在您的设计中扮演重要角色,并且您能够使用 Eclipse 的 AspectJ 开发工具 (AJDT) 插件,则 AspectJ 语言语法是首选选项。 它更干净、更简单,因为该语言是专门为编写方面而设计的。如果您不使用 Eclipse 或只有几个方面在您的应用程序中没有发挥重要作用,您可能需要考虑使用 @AspectJ 风格,在您的 IDE 中坚持使用常规 Java 编译,并添加一个方面编织阶段到 你的构建脚本。

6.2 用于 Spring AOP 的 @AspectJ 或 XML?

如果您选择使用 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 风格的自定义方面。

7、混合方面类型

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

8、代理机制

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

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

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

  • 使用 CGLIB,不能通知 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 代理。

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' referencethis.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' referencepojo.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(),都将针对 this 引用,而不是代理。 这具有重要意义。 这意味着自调用不会导致与方法调用相关的通知有机会运行。

好的,那么该怎么办呢? 最好的方法(术语“最佳”在这里被松散地使用)是重构你的代码,这样自调用就不会发生。 这确实需要您做一些工作,但这是最好的、侵入性最小的方法。 下一个方法绝对是可怕的,我们犹豫要不要指出它,正是因为它太可怕了。 您可以(对我们来说很痛苦)将类中的逻辑完全绑定到 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 框架。

9、@AspectJ 代理的程序化创建

除了使用 <aop:config> 或 <aop:aspectj-autoproxy>在您的配置中声明方面之外,还可以以编程方式创建通知目标对象的代理。 有关 Spring 的 AOP API 的完整详细信息,请参阅下一章。 在这里,我们要关注使用@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();

有关更多信息,请参阅 javadoc。

10、在 Spring 应用程序中使用 AspectJ

到目前为止,我们在本章中介绍的所有内容都是纯 Spring AOP。 在本节中,如果您的需求超出了 Spring AOP 单独提供的功能,我们将研究如何使用 AspectJ 编译器或编织器代替 Spring AOP 或将其与 Spring AOP 一起使用。

Spring 附带了一个小的 AspectJ 方面库,它可以作为 spring-aspects.jar 在您的发行版中独立使用。 您需要将其添加到类路径中才能使用其中的方面。 使用 AspectJ 使用 Spring 和其他 Spring 方面对域对象进行依赖注入 AspectJ 讨论了该库的内容以及如何使用它。 使用 Spring IoC 配置 AspectJ 方面讨论了如何依赖注入使用 AspectJ 编译器编织的 AspectJ 方面。 最后,Spring 框架中使用 AspectJ 的加载时编织介绍了使用 AspectJ 的 Spring 应用程序的加载时编织。

10.1 使用 AspectJ 在 Spring 中依赖注入域对象

Spring 容器实例化并配置在您的应用程序上下文中定义的 bean。 给定包含要应用的配置的 bean 定义的名称,也可以要求 bean 工厂配置一个预先存在的对象。 spring-aspects.jar 包含一个注解驱动的方面,它利用这个能力来允许任何对象的依赖注入。 该支持旨在用于在任何容器的控制之外创建的对象。 域对象通常属于这一类,因为它们通常是使用 new 运算符以编程方式创建的,或者作为数据库查询的结果由 ORM 工具创建的。

@Configurable 注解将类标记为适合 Spring 驱动的配置。 在最简单的情况下,您可以将其纯粹用作标记注解,如以下示例所示:

package com.xyz.myapp.domain;import org.springframework.beans.factory.annotation.Configurable;@Configurable
public class Account {// ...
}

当以这种方式用作标记接口时,Spring 通过使用与完全限定类型名称 (com. xyz.myapp.domain.Account)。 由于 bean 的默认名称是其类型的完全限定名称,因此声明原型定义的一种简便方法是省略 id 属性,如以下示例所示:

<bean class="com.xyz.myapp.domain.Account" scope="prototype"><property name="fundsTransferService" ref="fundsTransferService"/>
</bean>

如果要显式指定要使用的原型 bean 定义的名称,可以直接在注解中进行,如下例所示:

package com.xyz.myapp.domain;import org.springframework.beans.factory.annotation.Configurable;@Configurable("account")
public class Account {// ...
}

Spring 现在查找名为 account 的 bean 定义,并将其用作定义来配置新的 Account 实例。

您还可以使用自动装配来完全避免指定专用 bean 定义。 要让 Spring 应用自动装配,请使用 @Configurable注解的 autowire 属性。 您可以分别指定 @Configurable(autowire=Autowire.BY_TYPE) 或 @Configurable(autowire=Autowire.BY_NAME) 用于按类型或名称自动装配。 作为替代方案,最好在字段或方法级别通过 @Autowired 或@Inject 为@Configurable bean 指定显式的、注解驱动的依赖注入(有关更多详细信息,请参阅基于注解的容器配置)。

最后,您可以使用 dependencyCheck 属性(例如,@Configurable(autowire=Autowire.BY_NAME,dependencyCheck=true))为新创建和配置的对象中的对象引用启用Spring 依赖项检查。 如果此属性设置为 true,则 Spring 会在配置后验证所有属性(不是基元或集合)都已设置。

请注意,单独使用注解没有任何作用。 spring-aspects.jar 中的 AnnotationBeanConfigurerAspect 作用于注解的存在。 本质上,方面说,“在初始化一个用 @Configurable 注解的类型的新对象返回后,根据注解的属性使用Spring 配置新创建的对象”。 在此上下文中,“初始化”指的是新实例化的对象(例如,使用 new 运算符实例化的对象)以及正在进行反序列化的 Serializable 对象(例如,通过 readResolve())。

上一段中的关键短语之一是“本质上”。 在大多数情况下,“从新对象的初始化返回之后”的确切语义是好的。 在这种情况下,“初始化之后”意味着在对象被构造之后注入依赖项。 这意味着依赖项不可用于类的构造函数体。 如果您希望在构造函数体运行之前注入依赖项,从而可以在构造函数体中使用,则需要在@Configurable 声明中定义它,如下所示:

@Configurable(preConstruction = true)

您可以在 AspectJ 编程指南的本附录中找到有关 AspectJ 中各种切入点类型的语言语义的更多信息。

为此,必须使用 AspectJ 编织器编织带注解的类型。 您可以使用构建时 Ant 或 Maven 任务来执行此操作(例如,参见 AspectJ 开发环境指南)或加载时编织(参见 Spring 框架中的 AspectJ 加载时编织)。 AnnotationBeanConfigurerAspect 本身需要由 Spring 配置(以获得对用于配置新对象的 bean 工厂的引用)。 如果使用基于 Java 的配置,则可以将 @EnableSpringConfigured 添加到任何 @Configuration 类中,如下所示:

@Configuration
@EnableSpringConfigured
public class AppConfig {}

如果您更喜欢基于 XML 的配置,Spring 上下文命名空间定义了一个方便的 context:spring-configured 元素,您可以按如下方式使用它:

<context:spring-configured/>

在配置方面之前创建的 @Configurable 对象的实例会导致向调试日志发出一条消息,并且不会发生对象的配置。 一个例子可能是 Spring 配置中的一个 bean,它在被 Spring 初始化时创建域对象。 在这种情况下,您可以使用 depends-on bean 属性手动指定 bean 依赖于配置方面。 以下示例显示了如何使用 depends-on 属性:

<bean id="myService"class="com.xzy.myapp.service.MyService"depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect"><!-- ... --></bean>

不要通过 bean 配置器方面激活 @Configurable 处理,除非您真的打算在运行时依赖其语义。 特别是,请确保不要在容器中注册为常规 Spring bean 的 bean 类上使用 @Configurable。 这样做会导致双重初始化,一次通过容器,一次通过方面。

10.1.1 单元测试@Configurable 对象

@Configurable 支持的目标之一是启用域对象的独立单元测试,而不会遇到与硬编码查找相关的困难。 如果@Configurable 类型没有被 AspectJ 编织,则在单元测试期间注解没有影响。 您可以在被测对象中设置模拟或存根属性引用并照常进行。 如果@Configurable 类型已经被AspectJ 编织了,你仍然可以像往常一样在容器外进行单元测试,但是每次你构造一个@Configurable 对象时你都会看到一条警告消息,表明它没有被Spring 配置。

10.1.2 使用多个应用程序上下文

用于实现 @Configurable 支持的 AnnotationBeanConfigurerAspect 是一个 AspectJ 单例方面。 单例方面的范围与静态成员的范围相同:每个类加载器都有一个方面实例来定义类型。 这意味着,如果您在同一个类加载器层次结构中定义多个应用程序上下文,则需要考虑在何处定义 @EnableSpringConfigured bean 以及将 spring-aspects.jar 放置在类路径上的何处。

考虑一个典型的 Spring Web 应用程序配置,它具有一个共享的父应用程序上下文,该上下文定义了公共业务服务、支持这些服务所需的一切,以及每个 servlet 的一个子应用程序上下文(其中包含特定于该 servlet 的定义)。 所有这些上下文共存于同一个类加载器层次结构中,因此 AnnotationBeanConfigurerAspect 只能保存对其中一个的引用。 在这种情况下,我们建议在共享(父)应用程序上下文中定义 @EnableSpringConfigured bean。 这定义了您可能想要注入到域对象中的服务。 结果是您无法通过使用@Configurable 机制(这可能不是您想要做的事情)配置具有在子(特定于servlet)上下文中定义的bean 的引用的域对象。

在同一个容器中部署多个 web 应用程序时,确保每个 web 应用程序使用自己的类加载器加载 spring-aspects.jar 中的类型(例如,通过将 spring-aspects.jar 放在 ‘WEB-INF/lib’ 中)。 如果 spring-aspects.jar 仅添加到容器范围的类路径(因此由共享的父类加载器加载),则所有 Web 应用程序共享相同的方面实例(这可能不是您想要的)。

10.2 AspectJ 的其他 Spring 切面

除了@Configurable 方面之外,spring-aspects.jar 还包含一个 AspectJ 方面,您可以使用它来驱动 Spring 对使用 @Transactional 注解进行注解的类型和方法的事务管理。 这主要适用于想要在 Spring 容器之外使用 Spring Framework 的事务支持的用户。

解释 @Transactional 注解的方面是 AnnotationTransactionAspect。 当您使用此方面时,您必须注释实现类(或该类中的方法或两者),而不是该类实现的接口(如果有)。 AspectJ 遵循 Java 的规则,即接口上的注解不能被继承。

类上的 @Transactional 注解指定了在类中执行任何公共操作的默认事务语义。

类中方法上的 @Transactional 注解会覆盖类注解(如果存在)给出的默认事务语义。 可以注解任何可见性的方法,包括私有方法。 直接注解非公共方法是获得执行此类方法的事务划分的唯一方法。

从 Spring Framework 4.2 开始,spring-aspects 提供了一个类似的方面,为标准 javax.transaction.Transactional 注解提供完全相同的功能。 检查 JtaAnnotationTransactionAspect 以获取更多详细信息。

对于想要使用 Spring 配置和事务管理支持但不想(或不能)使用注释的 AspectJ 程序员,spring-aspects.jar 还包含抽象方面,您可以扩展以提供您自己的切入点定义。 有关更多信息,请参阅 AbstractBeanConfigurerAspect 和 AbstractTransactionAspect 方面的来源。 例如,以下摘录显示了如何编写方面以使用与完全限定类名称匹配的原型 bean 定义来配置域模型中定义的所有对象实例:

public aspect DomainObjectConfiguration extends AbstractBeanConfigurerAspect {public DomainObjectConfiguration() {setBeanWiringInfoResolver(new ClassNameBeanWiringInfoResolver());}// the creation of a new bean (any object in the domain model)protected pointcut beanCreation(Object beanInstance) :initialization(new(..)) &&CommonPointcuts.inDomainModel() &&this(beanInstance);
}

10.3 使用 Spring IoC 配置 AspectJ 方面

当您在 Spring 应用程序中使用 AspectJ 方面时,很自然地希望并期望能够使用 Spring 配置这些方面。 AspectJ 运行时本身负责创建方面,通过 Spring 配置 AspectJ 创建的方面的方式取决于方面使用的 AspectJ 实例化模型(per-xxx 子句)。

大多数 AspectJ 方面都是单例方面。 这些方面的配置很容易。 您可以创建一个 bean 定义,该定义正常引用方面类型并包含 factory-method="aspectOf" bean 属性。 这确保 Spring 通过向 AspectJ 请求而不是尝试自己创建实例来获取方面实例。 以下示例显示了如何使用 factory-method=“aspectOf” 属性:

<bean id="profiler" class="com.xyz.profiler.Profiler"factory-method="aspectOf"> <property name="profilingStrategy" ref="jamonProfilingStrategy"/>
</bean>

非单一方面更难配置。 但是,可以通过创建原型 bean 定义并使用 spring-aspects.jar 中的 @Configurable 支持来配置方面实例,一旦它们具有由 AspectJ 运行时创建的 bean,就可以做到这一点。

如果你有一些@AspectJ方面想用AspectJ编织(例如,对领域模型类型使用加载时编织)和其他@AspectJ方面想用Spring AOP,而这些方面都在Spring中配置 ,您需要告诉 Spring AOP @AspectJ 自动代理支持配置中定义的 @AspectJ 方面的哪个确切子集应该用于自动代理。 您可以通过在 <aop:aspectj-autoproxy/> 声明中使用一个或多个 <include/> 元素来实现这一点。 每个 <include/> 元素指定一个名称模式,并且只有名称至少与其中一个模式匹配的 bean 才会用于 Spring AOP 自动代理配置。 下面的例子展示了如何使用 <include/> 元素:

<aop:aspectj-autoproxy><aop:include name="thisBean"/><aop:include name="thatBean"/>
</aop:aspectj-autoproxy>

不要被 <aop:aspectj-autoproxy/> 元素的名称误导。 使用它会导致创建 Spring AOP 代理。 这里使用了@AspectJ 风格的方面声明,但不涉及 AspectJ 运行时。

10.4 在 Spring 框架中使用 AspectJ 进行加载时编织

加载时编织 (LTW) 是指在将 AspectJ 切面加载到 Java 虚拟机 (JVM) 时将它们编织到应用程序的类文件中的过程。 本节的重点是在 Spring Framework 的特定上下文中配置和使用 LTW。 本节不是对 LTW 的一般介绍。 有关 LTW 细节和仅使用 AspectJ(根本不涉及 Spring)配置 LTW 的完整详细信息,请参阅 AspectJ 开发环境指南的 LTW 部分。

Spring 框架为 AspectJ LTW 带来的价值在于能够对编织过程进行更细粒度的控制。 “Vanilla”AspectJ LTW 是通过使用 Java (5+) 代理来实现的,该代理通过在启动 JVM 时指定 VM 参数来开启。 因此,它是 JVM 范围的设置,在某些情况下可能很好,但通常有点过于粗糙。 启用 Spring 的 LTW 允许您在每个类加载器的基础上打开 LTW,这更细粒度,并且在“单 JVM 多应用程序”环境中更有意义(例如在典型的应用程序服务器环境中找到的)。

此外,在某些环境中,此支持启用加载时编织,而无需对添加 -javaagent:path/to/aspectjweaver.jar 或者(正如我们在本节后面描述的)-javaagent:path/to/spring-instrument.jar。 开发人员配置应用程序上下文以启用加载时编织,而不是依赖通常负责部署配置(例如启动脚本)的管理员。

现在销售宣传已经结束,让我们首先浏览一个使用 Spring 的 AspectJ LTW 的快速示例,然后详细介绍示例中引入的元素。 有关完整示例,请参阅 Petclinic 示例应用程序。

10.4.1 第一个例子

假设您是一名应用程序开发人员,负责诊断系统中某些性能问题的原因。 我们将打开一个简单的分析方面,让我们快速获得一些性能指标,而不是打破分析工具。 然后,我们可以在之后立即将更细粒度的分析工具应用于该特定区域。

此处提供的示例使用 XML 配置。 您还可以在 Java 配置中配置和使用 @AspectJ。 具体来说,您可以使用 @EnableLoadTimeWeaving 注解作为 <context:load-time-weaver/> 的替代品(详情见下文)。

以下示例显示了分析方面,这并不奇怪。 它是一个基于时间的分析器,使用 @AspectJ 风格的方面声明:

package foo;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.util.StopWatch;
import org.springframework.core.annotation.Order;@Aspect
public class ProfilingAspect {@Around("methodsToBeProfiled()")public Object profile(ProceedingJoinPoint pjp) throws Throwable {StopWatch sw = new StopWatch(getClass().getSimpleName());try {sw.start(pjp.getSignature().getName());return pjp.proceed();} finally {sw.stop();System.out.println(sw.prettyPrint());}}@Pointcut("execution(public * foo..*.*(..))")public void methodsToBeProfiled(){}
}

我们还需要创建一个 META-INF/aop.xml 文件,以通知 AspectJ 编织器我们想要将 ProfilingAspect 编织到我们的类中。 这个文件约定,即在 Java 类路径上存在一个名为 META-INF/aop.xml 的文件(或多个文件)是标准的 AspectJ。 以下示例显示了 aop.xml 文件:

<!DOCTYPE aspectj PUBLIC "-//AspectJ//DTD//EN" "https://www.eclipse.org/aspectj/dtd/aspectj.dtd">
<aspectj><weaver><!-- only weave classes in our application-specific packages --><include within="foo.*"/></weaver><aspects><!-- weave in just this aspect --><aspect name="foo.ProfilingAspect"/></aspects></aspectj>

现在我们可以转到配置的 Spring 特定部分。 我们需要配置一个LoadTimeWeaver(稍后解释)。 这个加载时编织器是负责将一个或多个 META-INF/aop.xml 文件中的方面配置编织到应用程序中的类的基本组件。 好消息是它不需要很多配置(您可以指定更多选项,但稍后会详细介绍),如下面的示例所示:

<?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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><!-- a service object; we will be profiling its methods --><bean id="entitlementCalculationService"class="foo.StubEntitlementCalculationService"/><!-- this switches on the load-time weaving --><context:load-time-weaver/>
</beans>

现在所有必需的工件(方面、META-INF/aop.xml 文件和 Spring 配置)都已准备就绪,我们可以使用 main(…) 方法创建以下驱动程序类来演示 LTW 的运行情况 :

package foo;import org.springframework.context.support.ClassPathXmlApplicationContext;public final class Main {public static void main(String[] args) {ApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml", Main.class);EntitlementCalculationService entitlementCalculationService =(EntitlementCalculationService) ctx.getBean("entitlementCalculationService");// the profiling aspect is 'woven' around this method executionentitlementCalculationService.calculateEntitlement();}
}

我们还有最后一件事要做。 本节的介绍确实说过,可以使用 Spring 在每个类加载器的基础上有选择地打开 LTW,这是真的。 但是,对于此示例,我们使用 Java 代理(随 Spring 提供)来开启 LTW。 我们使用以下命令运行前面显示的 Main 类:

java -javaagent:C:/projects/foo/lib/global/spring-instrument.jar foo.Main

-javaagent 是一个标志,用于指定和启用代理以检测在 JVM 上运行的程序。 Spring Framework 附带了这样一个代理,即 InstrumentationSavingAgent,它打包在 spring-instrument.jar 中,在前面的示例中作为 -javaagent 参数的值提供。

执行 Main 程序的输出类似于下一个示例。(我已经将 Thread.sleep(…) 语句引入到 calculateEntitlement() 实现中,以便分析器实际捕获 0 毫秒以外的内容(01234 毫秒不是 AOP 引入的开销)。以下清单显示了我们运行分析器时得到的输出:

Calculating entitlementStopWatch 'ProfilingAspect': running time (millis) = 1234
------ ----- ----------------------------
ms     %     Task name
------ ----- ----------------------------
01234  100%  calculateEntitlement

由于此 LTW 是通过使用成熟的 AspectJ 来实现的,因此我们不仅限于建议 Spring bean。 Main 程序的以下细微变化产生了相同的结果:

package foo;import org.springframework.context.support.ClassPathXmlApplicationContext;public final class Main {public static void main(String[] args) {new ClassPathXmlApplicationContext("beans.xml", Main.class);EntitlementCalculationService entitlementCalculationService =new StubEntitlementCalculationService();// the profiling aspect will be 'woven' around this method executionentitlementCalculationService.calculateEntitlement();}
}

请注意,在前面的程序中,我们如何引导 Spring 容器,然后完全在 Spring 的上下文之外创建 StubEntitlementCalculationService 的新实例。 分析建议仍然被纳入其中。

诚然,这个例子很简单。 但是,Spring 中 LTW 支持的基础知识在前面的示例中已经全部介绍过了,本节的其余部分详细解释了每个配置和用法背后的“原因”。

本示例中使用的 ProfilingAspect 可能是基本的,但它非常有用。 这是开发人员可以在开发过程中使用的开发时方面的一个很好的例子,然后可以轻松地从正在部署到 UAT 或生产中的应用程序的构建中排除。

10.4.2 方面

您在 LTW 中使用的方面必须是 AspectJ 方面。 您可以使用 AspectJ 语言本身编写它们,也可以使用 @AspectJ 样式编写方面。 您的方面是有效的 AspectJ 和 Spring AOP 方面。 此外,编译的方面类需要在类路径上可用。

10.4.3 ‘META-INF/aop.xml’

AspectJ LTW 基础结构是通过使用 Java 类路径上的一个或多个 META-INF/aop.xml 文件(直接地或更典型地在 jar 文件中)来配置的。

该文件的结构和内容在 AspectJ 参考文档的 LTW 部分中有详细说明。 因为 aop.xml 文件是 100% AspectJ 的,我们在此不再进一步描述。

10.4.4 所需的库 (JARS)

至少,您需要以下库来使用 Spring Framework 对 AspectJ LTW 的支持:

  • spring-aop.jar
  • aspectjweaver.jar

如果您使用 Spring 提供的代理来启用检测,您还需要:

  • spring-instrument.jar

10.4.5 Spring配置

Spring LTW 支持的关键组件是 LoadTimeWeaver 接口(在 org.springframework.instrument.classloading 包中),以及 Spring 发行版附带的众多实现。 LoadTimeWeaver 负责在运行时将一个或多个 java.lang.instrument.ClassFileTransformers 添加到 ClassLoader,这为各种有趣的应用程序打开了大门,其中之一恰好是方面的 LTW。

如果您不熟悉运行时类文件转换的想法,请在继续之前查看 java.lang.instrument 包的 javadoc API 文档。 虽然该文档并不全面,但至少您可以看到关键的接口和类(供您阅读本节时参考)。

为特定的 ApplicationContext 配置 LoadTimeWeaver 就像添加一行一样简单。 (请注意,您几乎肯定需要使用 ApplicationContext 作为您的 Spring 容器 — 通常,一个 BeanFactory 是不够的,因为 LTW 支持使用 BeanFactoryPostProcessors。)

要启用 Spring Framework 的 LTW 支持,您需要配置一个LoadTimeWeaver,这通常是通过使用 @EnableLoadTimeWeaving 注解来完成的,如下所示:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig {}

或者,如果您更喜欢基于 XML 的配置,请使用 <context:load-time-weaver/> 元素。 请注意,该元素是在上下文命名空间中定义的。 以下示例显示了如何使用 <context:load-time-weaver/>

<?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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:load-time-weaver/></beans>

前面的配置为您自动定义和注册了许多特定于 LTW 的基础设施 bean,例如 LoadTimeWeaver 和 AspectJWeavingEnabler。 默认的 LoadTimeWeaver 是 DefaultContextLoadTimeWeaver 类,它尝试修饰自动检测到的 LoadTimeWeaver。 “自动检测”的 LoadTimeWeaver 的确切类型取决于您的运行时环境。 下表总结了各种 LoadTimeWeaver 实现:

运行环境 LoadTimeWeaver 执行
在Apache Tomcat 中运行 TomcatLoadTimeWeaver
在GlassFish 中运行(仅限于 EAR 部署) GlassFishLoadTimeWeaver
在 Red Hat 的JBoss AS或WildFly 中运行 JBossLoadTimeWeaver
在 IBM 的WebSphere 中运行 WebSphereLoadTimeWeaver
在 Oracle 的WebLogic 中运行 WebLogicLoadTimeWeaver
JVM 从 Spring InstrumentationSavingAgent ( java -javaagent:path/to/spring-instrument.jar)开始 InstrumentationLoadTimeWeaver
回退,期望底层 ClassLoader 遵循通用约定(即可addTransformer选的getThrowawayClassLoader方法) ReflectiveLoadTimeWeaver

请注意,该表仅列出了LoadTimeWeavers使用DefaultContextLoadTimeWeaver。 您可以准确指定LoadTimeWeaver 要使用的实现。

要使用 Java 配置指定特定的 LoadTimeWeaver,请实现 LoadTimeWeavingConfigurer 接口并覆盖 getLoadTimeWeaver() 方法。 以下示例指定了一个 ReflectiveLoadTimeWeaver:

@Configuration
@EnableLoadTimeWeaving
public class AppConfig implements LoadTimeWeavingConfigurer {@Overridepublic LoadTimeWeaver getLoadTimeWeaver() {return new ReflectiveLoadTimeWeaver();}
}

如果您使用基于 XML 的配置,则可以将完全限定的类名指定为 <context:load-time-weaver/> 元素上的 weaver-class 属性的值。 同样,以下示例指定了一个 ReflectiveLoadTimeWeaver:

<?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"xsi:schemaLocation="http://www.springframework.org/schema/beanshttps://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttps://www.springframework.org/schema/context/spring-context.xsd"><context:load-time-weaverweaver-class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/></beans>

稍后可以使用众所周知的名称 loadTimeWeaver 从 Spring 容器中检索由配置定义和注册的 LoadTimeWeaver。 请记住,LoadTimeWeaver 仅作为 Spring 的 LTW 基础设施添加一个或多个 ClassFileTransformer 的一种机制而存在。 执行 LTW 的实际 ClassFileTransformer 是 ClassPreProcessorAgentAdapter(来自 org.aspectj.weaver.loadtime 包)类。 有关更多详细信息,请参阅 ClassPreProcessorAgentAdapter 类的类级 javadoc,因为编织实际影响的细节超出了本文档的范围。

配置的最后一个属性还有待讨论:aspectjWeaving 属性(如果使用 XML,则为 aspectj-weaving)。 此属性控制是否启用 LTW。 它接受三个可能值之一,如果属性不存在,则默认值为自动检测。 下表总结了三个可能的值:

注解值 XML 值 解释
ENABLED on AspectJ 编织已开启,方面在加载时适当地编织。
DISABLED off LTW 关闭。在加载时没有编织任何方面。
AUTODETECT autodetect 如果 Spring LTW 基础设施可以找到至少一个META-INF/aop.xml文件,那么 AspectJ 编织就开启了。否则,它是关闭的。这是默认值。

10.4.6 环境特定配置

最后一部分包含在应用程序服务器和 Web 容器等环境中使用 Spring 的 LTW 支持时所需的任何其他设置和配置。

  • Tomcat、JBoss、WebSphere、WebLogic

Tomcat、JBoss/WildFly、IBM WebSphere Application Server 和 Oracle WebLogic Server 都提供了一个能够进行本地检测的通用应用类加载器。 Spring 的原生 LTW 可以利用这些 ClassLoader 实现来提供 AspectJ 编织。 如前所述,您可以简单地启用加载时编织。 具体来说,不需要修改JVM启动脚本添加-javaagent:path/to/spring-instrument.jar。

请注意,在 JBoss 上,您可能需要禁用应用服务器扫描以防止它在应用程序实际启动之前加载类。 一个快速的解决方法是将一个名为 WEB-INF/jboss-scanning.xml 的文件添加到您的工件中,其中包含以下内容:

<scanning xmlns="urn:jboss:scanning:1.0"/>
  • 通用 Java 应用程序

当特定 LoadTimeWeaver 实现不支持的环境中需要类检测时,JVM 代理是通用解决方案。 对于这种情况,Spring 提供了 InstrumentationLoadTimeWeaver,它需要一个 Spring 特定(但非常通用)的 JVM 代理 spring-instrument.jar,由常见的 @EnableLoadTimeWeaving<context:load-time-weaver/> 设置自动检测。

要使用它,您必须通过提供以下 JVM 选项来启动带有 Spring 代理的虚拟机:

-javaagent:/path/to/spring-instrument.jar

请注意,这需要修改 JVM 启动脚本,这可能会阻止您在应用程序服务器环境中使用它(取决于您的服务器和您的操作策略)。 也就是说,对于每个 JVM 一个应用程序的部署,例如独立的 Spring Boot 应用程序,您通常在任何情况下都可以控制整个 JVM 设置。

11、更多资源

更多关于 AspectJ 的信息可以在 AspectJ 网站上找到。

Adrian Colyer 等人的 Eclipse AspectJ。 阿尔。 (Addison-Wesley,2005)提供了 AspectJ 语言的全面介绍和参考。

强烈推荐 AspectJ in Action,第二版,作者是 Ramnivas Laddad(Manning,2009 年)。 本书的重点是 AspectJ,但也探索了许多一般的 AOP 主题(在一定程度上)。

文章参考:https://docs.spring.io/spring-framework/docs/current/reference/html/core.html#aop-using-aspectj

spring框架学习 - 使用 Spring 的面向切面编程 补充1相关推荐

  1. spring框架学习 - 使用 Spring 的面向切面编程

    接上一篇博客:https://blog.csdn.net/qq_43605444/article/details/122029896?spm=1001.2014.3001.5502 七.使用 Spri ...

  2. 最新最全面的Spring详解(四)——面向切面编程

    前言 本文为 [Spring]面向切面编程 相关知识,下边将对AOP概述(包含什么是AOP.AOP相关术语.Spring AOP通知类型),Spring AOP能力和目标,AOP代理,@AspectJ ...

  3. JAVAEE框架整合技术之Spring02-AOP面向切面编程技术

    Spring新注解 Spring5.0之后的注解称为新注解 使用上面的注解还不能全部替代xml配置文件,还需要使用注解替代的配置 注解 说明 @Configuration 表示当前类是一个配置类,用于 ...

  4. spring框架学习 - 使用 Spring 的面向切面编程补充

    接上一篇博客:https://blog.csdn.net/qq_43605444/article/details/122047698?spm=1001.2014.3001.5502 4.4 声明通知 ...

  5. 一个简单的Spring的AOP例子(JAVA面向切面编程)

    公司要求分享技术,一个小组九个人,每周一个人,想想讲讲AOP吧,用处也挺大的,果断写了一个小例子,改了改也就成了.这是个简单AOP例子,包括前置通知,后置通知,环绕通知,和目标对象. 目标对象的接口: ...

  6. Spring框架学习笔记(1) ---[spring框架概念 , 初步上手使用Spring , 控制反转 依赖注入初步理解 ]

    spring官网 -->spring官网 spring5.3.12–>spring-framework 在线文档 --> Spring 5.3.12 文章目录 1.Spring概论 ...

  7. Spring框架学习【从入门到精通】

    文章目录 初识Spring框架 时代变迁 Spring的野心 官网 框架组成 核心概念 三大核心组件的关系 主要jar组成 Spring框架两大核心:IoC和DI 概念 IoC DI IoC的XML方 ...

  8. Spring框架 AOP面向切面编程(转)

    一.前言 在以前的项目中,很少去关注spring aop的具体实现与理论,只是简单了解了一下什么是aop具体怎么用,看到了一篇博文写得还不错,就转载来学习一下,博文地址:http://www.cnbl ...

  9. java框架013——Spring AOP面向切面编程

    一.Spring AOP简介 AOP的全称是Aspect-Oriented Programming,即面向切面编程(也称面向方面编程).它是面向对象编程(OOP)的一种补充,目前已成为一种比较成熟的编 ...

最新文章

  1. tensorflow2:tf.app.run()
  2. Androidの多线程之更新ui(AsyncTask)
  3. 【tensorflow】tf-tf.where(condition, x, y, name)
  4. script标签中defer和async的区别
  5. 新概念英语2电子版_新概念英语读100遍,英语能超神
  6. poj 1222 EXTENDED LIGHTS OUT 高斯消元
  7. 835数据结构c语言考研真题,武汉工程大学835《数据结构》2018考研大纲
  8. fftshift详解
  9. 从零开始部署深信服EDR
  10. 实时数据库简介和比较---PI
  11. 进存销管理系统(仓库管理系统)
  12. 实习第一天——网宿报到日
  13. js如何让段落首行缩进2个字符
  14. js仿苹果风格弹出框alert插件
  15. Oracle定时任务-查询-创建-删除-调用-定时任务时间参数
  16. vue 实现切换主题色(低配版)
  17. 会员制实现C2B定制有机农产品,被中粮我买投资的良食网这样卖有机生鲜
  18. 什么是核心交换机?如何选择核心交换机?
  19. 工控机服务器系统双网口网卡驱动安装
  20. 2021年1月6日订阅朋友的问题与解决方案汇总

热门文章

  1. openlayers5之ol.proj坐标转换
  2. UVA10110-灯光
  3. c语言乐学编程作业答案,信息乐学|浅谈C语言
  4. 大学python搜题app_有没有什么大学生搜题比较好用的app?
  5. http://tb.blog.csdn.net/TrackBack.aspx?PostId=1528237
  6. 全程软件测试之测试需求分析与计划(1)
  7. Android插件化开发指南——Hook技术(一)【长文】
  8. python实现扎破气球----童年经典游戏
  9. 【微信小程序】简单实现QQ发说说选择图片并显示功能
  10. d2-admin动态菜单权限(登陆后)