「这是我参与2022首次更文挑战的第20天,活动详情查看:2022首次更文挑战」
前言
说来惭愧一直没有全面体系的去彻底把spring的AOP给吃透。知道aop是动态代理包装bean注入、知道怎么用、可以优雅的解耦、优雅的通用等等。
好像已经知道了但是让你具体更深入的讲讲,比如:
- aop的机制是什么,具体怎么实现的?
- 什么时候用jdk动态代理?
- 什么时候用cglib代理?
- jdk代理和cglib代理的区别是啥?
- jdk代理一定比cglib代理慢吗?
- Spring AOP和AspectJ有什么关联?
- Spring AOP应用了那些设计模式?
等等吧,下面我们讲从官网(这里笔者阅读的是平时开发常用的版本,AOP属于spring核心部分后面的版本也大同小异)开始,会包含笔者的小结。以上问题都会一一作答,敬请期待。
这里我说一句,希望读者都能仔细看spring的文档,笔者觉得写的十分优秀,不求每句每个字都能全部掌握。如果能所有细节你都能如数家珍,那么你已经至少达到阿里P8水平了。
简介
面向方面编程(AOP) 通过提供另一种思考程序结构的方式来补充面向对象编程 (OOP)。OOP 中模块化的关键单元是类,而 AOP 中模块化的单元是切面。切面支持关注点的模块化,例如跨越多种类型和对象的事务管理。(这样的关注点在 AOP 文献中通常被称为横切关注点。)
Spring 的关键组件之一是AOP 框架。虽然 Spring IoC 容器不依赖 AOP,这意味着如果您不想使用 AOP,则不需要使用 AOP,但 AOP 补充了 Spring IoC 以提供非常强大的中间件解决方案。
Spring 2.0 AOP
Spring 2.0 引入了一种更简单、更强大的方式来使用schema-based的方法或@AspectJ 注解样式编写自定义切面。这两种风格都提供了完全类型化的advice和 AspectJ poincut语言的使用,同时仍然使用 Spring AOP 进行编织。
本章讨论 Spring 2.0 schema-和基于@AspectJ 的 AOP 支持。Spring 2.0 AOP 与 Spring 1.2 AOP 保持完全向后兼容,Spring 1.2 API 提供的较低级别的 AOP 支持将在下一章讨论。
AOP 在 Spring 框架中用于...
- … 提供声明式企业服务,尤其是作为 EJB 声明式服务的替代品。最重要的此类服务是 声明式事务管理。
- … 允许用户实现自定义方面,用 AOP 补充他们对 OOP 的使用。
如果您只对通用声明式服务或其他预打包的声明式中间件服务(例如池)感兴趣,则无需直接使用 Spring AOP,并且可以跳过本章的大部分内容。
笔者小结
aop是一种对程序结构的设计,使对主程序的扩展模块化,以更加优雅的方式进行解耦,从更抽象的角度来看提供的是一种切面的编程方式,对原程序入侵很小,是一种很轻量很强大的中间件。
AOP概念
让我们从定义一些核心 AOP 概念和术语开始。这些术语不是 Spring 特定的……不幸的是,AOP 术语不是特别直观;然而,如果 Spring 使用它自己的术语,那就更令人困惑了。
-
Aspect:跨多个类的关注点的模块化。事务管理是企业 Java 应用程序中横切关注点的一个很好的例子。aspects可以使用常规类(使用schema-based approach)或者使用@Aspect注解(
@AspectJ样式)注释常规类来实现。 -
Join point:程序执行过程中的一个点,例如方法的执行或异常的处理。在 Spring AOP 中,一个连接点总是 代表一个方法执行。
-
Advice:aspect在特定连接点采取的行动。不同类型的advice包括“around”、“before”和“after” advice。(建议类型将在下面讨论。)包括 Spring 在内的许多 AOP 框架将advice建模为拦截器,在join point 周围维护一个拦截器链。
-
Pointcut:匹配连接点的谓词。Advice 与pointcut表达式相关联,并在与切入点匹配的任何连接点处运行(例如,执行具有特定名称的方法)。pointcut表达式匹配的join point的概念是 AOP 的核心,Spring 默认使用 AspectJ 切入点表达式语言。
-
Introduction:代表类型声明其他方法或字段。Spring AOP 允许您向任何advised的对象引入新接口(和相应的实现)。例如,您可以使用Introduction使 bean 实现
IsModified接口,以简化caching。(Introduction在 AspectJ 社区中称为inter-type声明。) -
Target object:一个或多个切面advised的对象。也被称为建议对象。由于Spring AOP是使用运行时代理实现的,因此该对象将始终是代理对象。
-
AOP代理:由 AOP 框架创建的对象,用于实现切面协议(建议方法执行等)。在 Spring Framework 中,AOP 代理将是 JDK 动态代理或 CGLIB 代理。
-
Weaving:将切面与其他应用程序类型或对象链接起来以创建advised对象。这可以在编译时(例如,使用 AspectJ 编译器)、加载时或运行时完成。Spring AOP 与其他纯 Java AOP 框架一样,在运行时执行weaving。
Advice类型:
-
Before advice:在join point执行之前的advice,但不能阻止执行流继续到join point(除非它抛出异常)。
-
After returing advice:在join point正常完成后执行的advice:例如,如果方法返回而没有抛出异常。
-
After throwing advice:如果方法因抛出异常而退出,则要执行的建议。
-
After (finally) advice:无论join point以何种方式退出(正常或异常返回),都要执行的advice。
-
Around advice:围绕join point的advice,例如方法调用。这是最有力的advice。around advice可以在方法调用之前和之后执行自定义行为。它还负责选择是继续到join point还是通过返回自己的返回值或抛出异常来缩短advised的方法执行。
Around advice是最常见的advice。由于 Spring AOP 与 AspectJ 一样,提供了全方位的advice类型,因此我们建议您使用可以实现所需行为的最不强大的advice类型。例如,如果您只需要使用方法的返回值来更新缓存,那么您最好实现一个after advice而不是一个around advice,尽管一个around advice可以完成同样的事情。使用最具体的advice类型提供了一个更简单的编程模型,并且出错的可能性更小。比如,你不需要调用around advice的JoinPoint#proceed()方法,因此不可能调用它失败。
在 Spring 2.0 中,所有通知参数都是静态类型的,因此您可以使用适当类型的advice参数(例如方法执行的返回值的类型)而不是Object数组。
与切入点相匹配的连接点的概念是 AOP 与仅提供拦截的旧技术区分开来的关键。切入点使advice能够独立于面向对象的层次结构定位。例如,提供声明式事务管理的around advice可以应用于跨越多个对象(例如服务层中的所有业务操作)的一组方法。
笔者小结
AOP和仅提供拦截的旧技术相比可以定义切入点进行连接,提供了跨多个类的关注点的模块化。可以围绕连接点做不同类型的advice,建议使用最具体的advice。切面的目标对象在Spring AOP中始终是代理对象,使用的是JDK动态代理或CGLIB代理。
Spring AOP能力和目标
Spring AOP 是用纯 Java 实现的。不需要特殊的编译过程。Spring AOP 不需要控制类加载器层次结构,因此适用于 Servlet 容器或应用程序服务器。
Spring AOP 当前仅支持方法执行连接点(建议在 Spring bean 上执行方法)。没有实现Filed拦截,尽管可以在不破坏核心 Spring AOP API 的情况下添加对Filed拦截的支持。如果您需要advise filed access和更新连接点,请考虑使用 AspectJ 等语言。
Spring AOP 的 AOP 方法不同于大多数其他 AOP 框架。目的不是提供最完整的 AOP 实现(虽然 Spring AOP 很能干);而是提供 AOP 实现和 Spring IoC 之间的紧密集成,以帮助解决企业应用程序中的常见问题。
因此,例如,Spring 框架的 AOP 功能通常与 Spring IoC 容器一起使用。Aspects使用普通的 bean 定义语法进行配置(尽管这允许强大的“autoproxying”功能):这是与其他 AOP 实现的关键区别。有些事情你不能使用 Spring AOP 轻松或有效地完成,例如advise非常细粒度的对象(例如典型的域对象)在这种情况下,AspectJ 是最佳选择。然而,我们的经验是,Spring AOP 为企业 Java 应用程序中大多数适合 AOP 的问题提供了出色的解决方案。
Spring AOP 永远不会努力与 AspectJ 竞争以提供全面的 AOP 解决方案。我们相信像 Spring AOP 这样的基于代理的框架和像 AspectJ 这样的成熟框架都是有价值的,它们是互补的,而不是竞争的。Spring 将 Spring AOP 和 IoC 与 AspectJ 无缝集成,以使 AOP 的所有用途都能在一致的基于 Spring 的应用程序架构中得到满足。这种集成不会影响 Spring AOP API 或 AOP Alliance API:Spring AOP 保持向后兼容。有关 Spring AOP API 的讨论,请参见下一章。
Spring 框架的核心原则之一是非侵入性;这就是不应该强迫您将特定于框架的类和接口引入您的 业务/域 模型的想法。但是,在某些地方,Spring 框架确实为您提供了将 Spring 框架特定的依赖项引入代码库的选项:提供这些选项的理由是因为在某些情况下,它可能更容易阅读或编写一些特定的代码以这种方式实现功能。不过,Spring 框架(几乎)总是为您提供选择:您可以自由地做出明智的决定,以决定哪个选项最适合您的特定用例或场景。
与本章相关的一个这样的选择是选择哪种 AOP 框架(以及哪种 AOP 风格)。您可以选择 AspectJ 和/或 Spring AOP,还可以选择 @AspectJ 注释样式方法或 Spring XML 配置样式方法。本章选择首先介绍 @AspectJ 风格的方法这一事实不应被视为 Spring 团队更喜欢 @AspectJ 注释风格的方法而不是 Spring XML 配置风格的迹象。
请参阅第 10.4 节,“选择要使用的 AOP 声明样式”以更完整地讨论每种样式的原因和原因。
笔者小结
Spring AOP只支持在方法上进行切面,旨在和Spring IOC紧密集成,以帮助解决企业应用程序中常见的问题。如果你想在其他地方进行切面,如filed上或者更细粒度的域对象上可以考虑使用AspectJ,Spring是可以和它集成的。
AOP代理
Spring AOP 默认为 AOP 代理使用标准 JDK动态代理。这使得任何接口(或一组接口)都可以被代理。
Spring AOP 也可以使用 CGLIB 代理。这是代理类而不是接口所必需的。如果业务对象未实现接口,则默认使用 CGLIB。因为对接口而不是类进行编程是一种好习惯;业务类通常会实现一个或多个业务接口。在需要advice未在接口上声明的方法或需要将代理对象作为具体类型传递给方法的那些(希望很少见)情况下,可以 强制使用 CGLIB 。
重要的是要掌握 Spring AOP 是基于代理的这一事实。请参阅 第 10.6.1 节,“了解 AOP 代理”,以彻底检查此实现细节的实际含义。
笔者小结
Spring AOP默认使用标准的JDK动态代理,但是未在接口上声明需要切面的方法则使用CGLIB代理。
@AspectJ支持
@AspectJ 指的是一种将切面声明为带有注解的常规 Java 类的风格。@AspectJ 样式是由 AspectJ 项目作为 AspectJ 5 版本的一部分引入的。Spring 解释与 AspectJ 5 相同的注释,使用 AspectJ 提供的库进行切入点解析和匹配。AOP 运行时仍然是纯 Spring AOP,并且不依赖于 AspectJ 编译器或编织器。
使用 AspectJ 编译器和编织器可以使用完整的 AspectJ 语言,并在第 10.8 节“将 AspectJ 与 Spring 应用程序一起使用”中进行了讨论。
笔者小结
@AspectJ样式是由AspectJ项目作为AspectJ 5版本的一部分引入的,使用的是AspectJ提供的库进行切入点解析和匹配。AOP 运行时仍然是纯 Spring AOP,并且不依赖于 AspectJ 编译器或编织器。
启用@AspectJ支持
要在 Spring 配置中使用 @AspectJ aspects,您需要启用 Spring 支持以基于 @AspectJ aspects配置 Spring AOP,自动代理beans基于他们是否被建议使用这些aspects。通过自动代理,我们的意思是如果 Spring 确定一个 bean 由一个或多个aspects advised,它将自动为该 bean 生成一个代理来拦截方法调用并确保根据需要执行advice。
可以使用 XML 或 Java 样式配置启用 @AspectJ 支持。在任何一种情况下,您还需要确保 AspectJ 的aspectjweaver.jar库位于应用程序的类路径中(版本 1.6.8 或更高版本)。该库可在 AspectJ 发行版的lib目录中或通过 Maven 中央存储库获得。
使用 Java 配置启用 @AspectJ 支持
要使用 Java 启用 @AspectJ 支持,@Configuration请添加@EnableAspectJAutoProxy 注释:
@Configuration
@EnableAspectJAutoProxy
public class AppConfig {
}
通过 XML 配置启用 @AspectJ 支持
要使用基于 XML 的配置启用 @AspectJ 支持,请使用以下aop:aspectj-autoproxy 元素:
<aop:aspectj-autoproxy/>
这假设您正在使用第 40 章,基于 XML 模式的配置中描述的模式支持。请参阅 第 40.2.7 节,“aop 模式”了解如何在aop命名空间中导入标签。
笔者小结
读者如果使用springboot就不用手动配置,springboot会进行自动装配。
声明一个aspect
启用@AspectJ 支持后,在应用程序上下文中定义的具有@AspectJ aspect(具有@Aspect注释)的类的任何bean 都将被Spring 自动检测并用于配置Spring AOP。以下示例显示了一个不太有用的aspect所需的最小定义:
应用程序上下文中的常规 bean 定义,指向具有@Aspect注释的 bean 类:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect">
<!-- configure properties of aspect here as normal -->
</bean>
以及NotVeryUsefulAspect类定义,用 org.aspectj.lang.annotation.Aspect注解进行注解;
package org.xyz;
import org.aspectj.lang.annotation.Aspect;
@Aspect
public class NotVeryUsefulAspect {
}
Aspects(用@Aspect注释的类)可能像任何其他类一样具有方法和字段。它还可能包含pointcut、advice和introduction(inter-type)的声明。
您可以在 Spring XML 配置中将aspect类注册为常规 bean,或者通过类路径扫描自动检测它们-就像任何其他 Spring 管理的 bean 一样。但是,请注意 @Aspect注解不足以在类路径中进行自动检测:为此,您需要添加单独的 @Component注解(或者,根据Spring组件扫描器的规则,定制一个符合条件的原型注解)。
在 Spring AOP 中,不可能让aspect本身成为其他aspect的advice目标。类上的 @Aspect注解将其标记为aspect,因此将其排除在自动代理之外。
笔者小结
使用@Aspect定义aspect bean会被Spring自动检测并用于配置Spring AOP。同时需要aspect不能切aspect。
声明一个pointcut
回想一下,切入点决定了连接的兴趣点,从而使我们能够控制advice何时执行。Spring AOP 仅支持 Spring beans 的方法执行连接点,因此您可以将切入点视为匹配 Spring beans 上的方法执行。切入点声明有两部分:包含名称和任何参数的签名,以及准确确定我们感兴趣的方法执行的切入点表达式。在 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 的书籍之一,Adrian Colyer等人(Addison Wesley,2005)的书《Eclipse AspectJ》或Ramnivas Laddad(Manning,2009 年)的《 AspectJ in Action, Second Edition 》。
笔者小结
pointcut定义aspect感兴趣的join point,从而使我们能够控制advice何时执行。pointcut支持方法签名和切点表达式,用于简化感兴趣的join point。
支持的切入点指示符
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 中执行的方法)具有给定注解的连接点
因为 Spring AOP 将匹配限制为仅方法执行连接点,所以上面对切入点指示符的讨论给出了比您在 AspectJ 编程指南中找到的更窄的定义。此外,AspectJ 本身具有基于类型的语义,并且在执行连接点处,“this”和“target”都指的是同一个对象——执行该方法的对象。Spring AOP 是一个基于代理的系统,它区分代理对象本身(绑定到'this')和代理背后的目标对象(绑定到'target')。
由于 Spring 的 AOP 框架基于代理的性质,根据定义,producted的方法不会被截获,对于 JDK 代理(这不适用)和 CGLIB 代理(这在技术上可行但不推荐用于 AOP 目的)都是如此。因此,任何给定的切入点都将仅与public method匹配!
如果您的拦截需求包括producted/private方法甚至构造函数,请考虑使用 Spring 驱动的native AspectJ weaving而不是 Spring 的基于代理的 AOP 框架。这就构成了具有不同特点的不同AOP使用模式,所以在做决定之前一定要先熟悉编织。
Spring AOP 还支持一bean name的附加 PCD。此 PCD 允许您将连接点的匹配限制为特定Spring bean name,或一组Spring bean name(使用通配符时)。'bean' PCD 具有以下形式:
bean(idOrNameOfBean)
“idOrNameOfBean”表示可以是任何 Spring bean 的名称:提供了使用“*”字符的有限通配符支持,因此如果您为 Spring bean 建立一些命名约定,您可以很容易地编写一个“bean”PCD 表达式来挑出他们。与其他切入点指示符的情况一样,“bean”PCD 可以是 &&、||和 ! (否定)也是。
笔者小结
笔者最常用的为execution和@annotation,还有需要注意的是Spring AOP只匹配public method并且不支持内部方法调用进行切面。
组合切入点表达式
切入点表达式可以使用 '&&'、'||' 组合 和 '!'。也可以按名称引用切入点表达式。以下示例显示了三个切入点表达式:anyPublicOperation(如果方法执行连接点表示任何公共方法的执行,则匹配);inTrading(如果方法执行在交易模块中,则匹配),和tradingOperation(如果方法执行代表交易模块中的任何公共方法,则匹配)。
@Pointcut("execution(public * *(..))")
private void anyPublicOperation() {}
@Pointcut("within(com.xyz.someapp.trading..*)")
private void inTrading() {}
@Pointcut("anyPublicOperation() && inTrading()")
private void tradingOperation() {}
最佳实践是从更小的命名组件构建更复杂的切入点表达式,如上所示。当按名称引用切入点时,适用正常的 Java 可见性规则(您可以看到相同类型的private切入点、层次结构中的protected切入点、任何地方的public切入点等等)。可见性不影响切入点 匹配。
笔者小结
意思就是可以使用方法名组合感兴趣的join point。
共享通用切入点定义
在使用企业应用程序时,您通常希望从多个aspects引用应用程序的模块和特定的操作集。我们建议为此目的定义一个捕获常用切入点表达式的“SystemArchitecture” aspect。一个典型的这样的方面如下所示:
package com.xyz.someapp;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
@Aspect
public class SystemArchitecture {
/**
* A join point is in the web layer if the method is defined
* in a type in the com.xyz.someapp.web package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.web..*)")
public void inWebLayer() {}
/**
* A join point is in the service layer if the method is defined
* in a type in the com.xyz.someapp.service package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.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.someapp.dao package or any sub-package
* under that.
*/
@Pointcut("within(com.xyz.someapp.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.someapp.abc.service and com.xyz.someapp.def.service) then
* the pointcut expression "execution(* com.xyz.someapp..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.someapp..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.someapp.dao.*.*(..))")
public void dataAccessOperation() {}
}
在这样一个aspect定义的切入点可以在任何需要切入点表达式的地方引用。例如,要使服务层具有事务性,您可以编写:
<aop:config>
<aop:advisor
pointcut="com.xyz.someapp.SystemArchitecture.businessService()"
advice-ref="tx-advice"/>
</aop:config>
<tx:advice id="tx-advice">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<aop:config>和<aop:advisor>元素在Section 10.3, “Schema-based AOP support”中讨论。事务元素在第 16 章“事务管理”中讨论。
笔者小结
这个我没使用过,就是公用切入点,以后可以尝试。
例子
Spring AOP 用户可能execution最常使用切入点指示符。执行表达式的格式为:
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.*(..))
- 执行服务包中定义的任何方法:
execution(* com.xyz.service.*.*(..))
- 执行服务包或子包中定义的任何方法:
execution(* com.xyz.service..*.*(..))
- 服务包中的任何连接点(仅在 Spring AOP 中执行方法):
within(com.xyz.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 中执行方法),并且在运行时传递的参数是
Serializable:
args(java.io.Serializable)
请注意,此示例中给出的切入点不同于execution(* *(java.io.Serializable)):如果在运行时传递的参数是可序列化的,则 args 版本匹配,如果方法签名声明了类型的单个参数,则execution版本匹配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)
- 名称与通配符表达式“*Service”匹配的 Spring bean 上的任何连接点(仅在 Spring AOP 中执行方法)
bean(*Service)
笔者小结
上面一些概念理解不了的,看例子。
编写好的切入点
在编译期间,AspectJ 处理切入点以尝试优化匹配性能。检查代码并确定每个连接点是否(静态或动态)匹配给定的切入点是一个代价高昂的过程。(动态匹配意味着无法从静态分析中完全确定匹配,并且将在代码中放置测试以确定代码运行时是否存在实际匹配)。在第一次遇到切入点声明时,AspectJ 会将其重写为匹配过程的最佳形式。这是什么意思?基本上,切入点在 DNF(析取范式)中重写,并且切入点的组件被排序,以便首先检查那些评估成本较低的组件。
但是,AspectJ 只能使用它被告知的内容,并且为了获得最佳匹配性能,您应该考虑他们试图实现的目标,并在定义中尽可能缩小匹配的搜索空间。现有的指示符自然属于以下三组之一:inded, scoping 和 context:
- 种类指示符是那些选择特定种类的连接点的指示符。例如execution, get, set, call, handler
- 范围指示符是选择一组兴趣点(可能有多种)的指示符。例如:within, withincode
- 上下文指示符是那些基于上下文匹配(并且可选地绑定)的指示符。例如:this、target、@annotation
一个写得很好的切入点应该尝试至少包括前两种类型(种类和范围),而如果希望基于连接点上下文进行匹配,或者绑定该上下文以在建议中使用,则可以包括上下文指示符。仅提供一个 kinded 指示符或仅提供一个上下文指示符将起作用,但由于所有额外的处理和分析,可能会影响编织性能(使用的时间和内存)。范围指示符的匹配速度非常快,并且它们的使用意味着 AspectJ 可以非常快速地消除不应进一步处理的连接点组——这就是为什么一个好的切入点应该尽可能包含一个。
申明advice
Advice 与切入点表达式相关联,并在切入点匹配的方法执行之前、之后或周围运行。切入点表达式可以是对命名切入点的简单引用,也可以是就地声明的切入点表达式。
Before advice
Before advice在一个aspect中使用@Before注解进行声明:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect
public class BeforeExample {
@Before("com.xyz.myapp.SystemArchitecture.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
After returning advice,当匹配的方法执行正常返回时运行。它是使用@AfterReturning注解声明的:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}
}
有时您需要在advice正文中访问返回的实际值。您可以使用以下形式@AfterReturning绑定返回值:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterReturning;
@Aspect
public class AfterReturningExample {
@AfterReturning(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// ...
}
}
属性中使用的名称returning必须与通知方法中的参数名称相对应。当方法执行返回时,返回值将作为相应的参数值传递给通知方法。returning子句还将匹配限制为只匹配那些返回指定类型的值的方法执行(在本例中为对象,它将匹配任何返回值)。
请注意,使用after-returning advice时,不可能返回完全不同的引用。
After throwing advice
After throwing advice,当匹配的方法抛出一个异常退出时运行。它是使用@AfterThrowing注解声明的:
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ...
}
}
通常,您希望advice仅在引发给定类型的异常时运行,并且您还经常需要在advice body中访问引发的异常。使用该 throwing属性来限制匹配(如果需要,则Throwable用作异常类型)并将抛出的异常绑定到通知参数。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.AfterThrowing;
@Aspect
public class AfterThrowingExample {
@AfterThrowing(
pointcut="com.xyz.myapp.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ...
}
}
在throwing属性中使用的名称必须与通知方法中的参数名称相对应。当通过抛出异常退出方法执行时,异常将作为相应的参数值传递给advice方法。throwing子句还将匹配限制为仅抛出指定类型(在本例中为DataAccessException) 异常的那些方法执行。
After (finally) advice
After (finally) advice,在匹配的方法退出后执行。。它是使用@After注解声明的。After 通知必须准备好处理正常和异常返回条件。它通常用于释放资源等。
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.After;
@Aspect
public class AfterFinallyExample {
@After("com.xyz.myapp.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// ...
}
}
Around advice
最后一种建议是around advice。around advice“围绕”匹配的方法执行。它有机会在方法执行之前和之后进行工作,并确定该方法何时、如何甚至是否真正执行。如果您需要以线程安全的方式(例如启动和停止计时器)在方法执行之前和之后共享状态,则通常使用环绕通知。始终使用满足您要求的最不强大的advice形式(即,如果简单的 before advice可以使用,请不要使用 around advice)。
使用@Around注解声明环绕通知。advice方法的第一个参数必须是ProceedingJoinPoint。在advice的主体内,调用proceed()使底层方法执行。procedure方法也可以被调用通过传入Object[]-该数组值是被用作方法执行的参数。
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.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}
}
around advice返回的值将是方法调用者看到的返回值。例如,一个简单的缓存aspect可以从缓存中返回一个值(如果它有一个值),如果没有则调用proceed()。请注意,在 around 建议的主体内可能会调用一次、多次或根本不调用proceed,所有这些都是非常合法的。
Advice parameters
Spring 提供完全类型化的advice——这意味着您在advice签名中声明所需的参数(正如我们在上面的returning和throwing示例中看到的那样),而不是Object[]一直使用数组。稍后我们将看到如何使参数和其他上下文值可用于advice主体。首先让我们看一下如何编写通用advice,以了解advice当前advise的方法。
笔者小结
这节介绍如何获得join point方法上的入参。
Access to the current JoinPoint
任何advice方法都可以声明它的第一个参数,一个类型为org.aspectj.lang.JoinPoint的参数 (请注意,around advice需要声明一个类型为ProceedingJoinPoint的第一个参数,它是JoinPoint的一个子类。)JoinPoint接口提供了许多有用的方法,例如getArgs()(返回方法的arguments),getThis()(返回代理对象),getTarget()(返回目标对象),getSignature()(返回已知方法的描述)和toString()(打印方法有用的描述)。请查阅javadocs以获得完整的细节。
笔者小结
任何类型的advice都可以声明第一个类型为org.aspectj.lang.JoinPoint的参数。JoinPoint提供供了一些获得join point上下文的方法。
Passing parameters to advice
我们已经看到了如何绑定返回值或异常值(在使用after returning和after throwing advice之后)。要使参数值可用于advice正文,您可以使用args来进行绑定。如果在 args 表达式中使用参数名称代替类型名称,则在调用advice知时,相应参数的值将作为参数值传递。一个例子应该更清楚地说明这一点。假设您要advice执行以 Account 对象为第一个参数的 dao 操作,并且您需要访问advice正文中的帐户。您可以编写以下内容:
@Before("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
public void validateAccount(Account account) {
// ...
}
切入点表达式的args(account,..)部分有两个目的:首先,它将匹配限制为仅那些方法接受至少一个参数的方法执行,并且传递给该参数的参数是一个Account实例; 其次,它通过account参数使实际Account对象可用于advice 。
另一种写法是声明一个切入点,Account 当它匹配一个连接点时“提供”对象值,然后只从advice中引用命名的切入点。这将如下所示:
@Pointcut("com.xyz.myapp.SystemArchitecture.dataAccessOperation() && args(account,..)")
private void accountDataAccessOperation(Account account) {}
@Before("accountDataAccessOperation(account)")
public void validateAccount(Account account) {
// ...
}
感兴趣的读者可以再次参考 AspectJ 编程指南以获取更多详细信息。
代理对象 ( this)、目标对象 ( target) 和注释 ( @within, @target, @annotation, @args) 都可以以类似的方式绑定。以下示例显示了如何匹配带有 @Auditable注解的方法的执行,并提取audit代码。
首先是@Auditable注解的定义:
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Auditable {
AuditCode value();
}
然后匹配注有@Auditable方法执行的advice:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod() && @annotation(auditable)")
public void audit(Auditable auditable) {
AuditCode code = auditable.value();
// ...
}
笔者小结
切点可以使用args来进行join point和advice方法的入参数绑定,方便advice使用。this、target和@within, @target, @annotation, @args都可以使用此类方式把对应的对象传递给advice使用。
Advice参数和泛型
Spring 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
}
为了完成这项工作,我们必须检查集合的每个元素,这是不合理的,因为我们也无法决定如何处理null一般的值。要实现类似的效果,您必须键入参数Collection<?>并手动检查元素的类型。
笔者小结
Spring AOP支持范型,会进行匹配,当然不支持集合的范型因为会进行类型抹除。
确定参数名称
advice调用中的参数绑定依赖于切入点表达式中使用的名称与方法签名中声明的参数名称匹配。参数名称不能通过Java 反射获得,因此 Spring AOP 使用以下策略来确定参数名称:
- 如果参数名称已由用户明确指定,则使用指定的参数名称:advice和切入点注释都有一个可选的“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”属性的值中省略参数的名称。例如,如果您修改前面的advice以接收连接点对象,则“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的第一个参数进行的特殊处理。对于不收集任何其他连接点上下文的advice尤其方便。在这种情况下,您可以简单地省略“argNames”属性。例如,以下建议不需要声明“argNames”属性:
@Before("com.xyz.lib.Pointcuts.anyPublicMethod()")
public void audit(JoinPoint jp) {
// ... use jp
}
-
使用
'argNames'属性有点笨拙,所以如果'argNames'没有指定属性,那么 Spring AOP 会查看类的调试信息,并尝试从局部变量表中确定参数名称。只要使用调试信息('-g:vars'至少)编译了类,就会出现此信息。使用此标志进行编译的后果是:(1)您的代码将更容易理解(逆向工程),(2)类文件大小会稍微大一些(通常无关紧要),(3)要删除的优化编译器不会应用未使用的局部变量。换句话说,打开这个标志你应该不会遇到任何困难。 -
如果在没有必要调试信息的情况下编译了代码,那么 Spring AOP 将尝试推断绑定变量与参数的配对(例如,如果切入点表达式中只绑定了一个变量,并且advice 方法只接受一个参数,配对很明显!)。如果在给定可用信息的情况下变量的绑定不明确,则将抛出一个
AmbiguousBindingException。 -
如果上述所有策略都失败,
IllegalArgumentException则会抛出一个。
笔者小结
由于参数名不能通过java反射获得,这点在java 8之前的版本才会出现,现在普遍都是8以上了所以不用担心。
Proceeding with arguments
我们之前说过,我们将描述如何使用在Spring AOP和AspectJ中一致工作的参数编写继续调用。解决方案只是确保advice签名按顺序绑定每个方法参数。例如:
@Around("execution(List<Account> find*(..)) && " +
"com.xyz.myapp.SystemArchitecture.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 相同的优先级规则来确定通知执行的顺序。最高优先级的建议首先运行“在途中”(因此给定两条之前的建议,具有最高优先级的一条首先运行)。从连接点“退出”时,优先级最高的建议最后运行(因此,给定两条后建议,优先级最高的一条将运行第二条)。
当在不同方面定义的两条advice都需要在同一个连接点运行时,除非您另外指定,否则执行顺序是未定义的。您可以通过指定优先级来控制执行顺序。这是通过在org.springframework.core.Ordered方面类中实现接口或使用注解对其进行Order注解以正常的 Spring 方式完成的。给定两个aspects,从Ordered.getValue()(或注释值)返回较低值的方面具有较高的优先级。
当在同一aspect义的两条advice都需要在同一连接点运行时,排序是未定义的(因为无法通过 javac 编译类的反射来检索声明顺序)。考虑将此类建议方法折叠为每个aspect类中的每个连接点的一个advice方法,或者将建议部分重构为单独的aspect类 - 可以在aspect级别排序。
笔者小结
可以使用@Order为aspect排序,较低值会优先执行。如果一个aspect有两个相同类型的advice可以拆分成两个aspect方便进行排序。
Introductions
Introductions(在AspectJ中称为inter-type声明)使aspect能够声明advised对象实现给定接口,并代表这些对象提供该接口的实现。
一个introduction是使用@DeclareParents注解构成。此注解用于声明匹配类型有一个新的父级(因此得名)。例如,给定一个接口UsageTracked和该接口的实现DefaultUsageTracked,以下aspect声明服务接口的所有实现者也实现该UsageTracked接口。(例如,为了通过 JMX 公开统计信息。)
@Aspect
public class UsageTracking {
@DeclareParents(value="com.xzy.myapp.service.*+", defaultImpl=DefaultUsageTracked.class)
//把com.xzy.myapp.service下的类及子类都实现UsageTracked接口(使用默认实现DefaultUsageTracked)
public static UsageTracked mixin;
@Before("com.xyz.myapp.SystemArchitecture.businessService() && this(usageTracked)")
//切点为com.xyz.myapp.SystemArchitecture.businessService() 方法并且SystemArchitecture实现了UsageTracked接口。
public void recordUsage(UsageTracked usageTracked) {
usageTracked.incrementUseCount();
}
}
要实现的接口由注解字段的类型决定。注解@DeclareParents是 AspectJ的 value属性是 AspectJ 类型的模式:- 任何匹配类型的 bean 都将实现 UsageTracked 接口。请注意,在上述示例的before advice中,服务 bean 可以直接用作UsageTracked接口的实现。如果以编程方式访问 bean,您将编写以下内容:
UsageTracked usageTracked = (UsageTracked) context.getBean("myService");
笔者小结
就是对要切面的代理类实现指定接口,方便advice处理时使用。
Aspect实例化模型
(这是一个高级主题,所以如果您刚开始使用 AOP,您可以放心地跳过它直到以后。)
默认情况下,应用程序上下文中的每个方面都会有一个实例。AspectJ 将此称为单例实例化模型。可以定义具有备用生命周期的aspects:Spring 支持 AspectJperthis和pertarget 实例化模型(目前 percflow, percflowbelow,``pertypewithin不支持)。
通过在注解中指定一个perthis子句来声明“perthis”aspect。@Aspect让我们看一个例子,然后我们将解释它是如何工作的。
@Aspect("perthis(com.xyz.myapp.SystemArchitecture.businessService())")
public class MyAspect {
private int someState;
@Before(com.xyz.myapp.SystemArchitecture.businessService())
public void recordServiceUsage() {
// ...
}
该'perthis'子句的效果是将为每个执行业务service的唯一service对象创建一个aspect实例(每个唯一对象在切入点表达式匹配的连接点处绑定到“this”)。aspect实例是在第一次在service对象上调用方法时创建的。当service对象超出范围时,aspect超出范围。在创建aspect实例之前,其中的任何advice都不会执行。一旦创建了aspect实例,其中声明的advice将在匹配的连接点处执行,但仅当service对象是与此ascpect关联的时。有关每个子句的更多信息,请参阅 AspectJ 编程指南。
实例化模型的'pertarget'工作方式与 perthis 完全相同,但在匹配的连接点为每个唯一目标对象创建一个aspect实例。
笔者小结
就是让aspect是实例化加入Spring bean实例化的声明周期,支持perthis和pertarget 实例化模型,在切面对象实例化之前进行实例化。
示例
现在您已经了解了所有组成部分的工作原理,让我们将它们放在一起做一些有用的事情!
由于并发问题(例如,死锁失败者),业务服务的执行有时会失败。如果重试操作,下一轮很有可能成功。对于适合在这种情况下重试的业务服务(不需要返回给用户解决冲突的幂等操作),我们希望透明地重试该操作以避免客户端看到 PessimisticLockingFailureException。这是一个明确跨服务层中多个服务的需求,因此非常适合通过aspect实现。
因为我们想重试操作,所以我们需要使用 around advice,以便我们可以多次调用proceed。以下是基本方面实现的外观:
@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.SystemArchitecture.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接口,因此我们可以将aspect的优先级设置为高于事务通知(我们希望每次重试时都有新的事务)。属性maxRetries和order都将由 Spring 配置。主要动作发生在doConcurrentOperation around advice中。请注意,目前我们将重试逻辑应用于所有 businessService()s。我们尝试proceed,如果发生了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>
为了细化aspect使其只重试幂等操作,我们可以定义一个 Idempotent注解:
@Retention(RetentionPolicy.RUNTIME)
public @interface Idempotent {
// marker annotation
}
并使用注解对服务操作的实现进行注解。将aspect更改为仅重试幂等操作仅涉及改进切入点表达式,以便仅@Idempotent操作匹配:
@Around("com.xyz.myapp.SystemArchitecture.businessService() && " +
"@annotation(com.xyz.myapp.service.Idempotent)")
public Object doConcurrentOperation(ProceedingJoinPoint pjp) throws Throwable {
...
}
笔者小结
例子中给我们展示了通过aspect进行重试,需要注意的是重试的操作需要保证幂等性(失败了部分就全部失败,最终失败了之后重试也是失败,成功了之之后再重试也是成功)。同时还给我展示了实现Ordered接口完成对是否每次都开启新的事务的控制。同时还给我们展示了切面正确的使用方法尽量使用能缩小范围的切点指示符。
Schema-based AOP支持
如果您更喜欢基于 XML 的格式,那么 Spring 还支持使用新的“aop”命名空间标签定义aspect。支持与使用 @AspectJ 样式时完全相同的切入点表达式和advice类型,因此在本节中,我们将重点关注新语法,并请读者参考上一节中的讨论(第 10.2 节,“@AspectJ 支持”)了解编写切入点表达式和advice参数的绑定。
要使用本节中描述的 aop 命名空间标签,您需要Chapter 40, XML Schema-based configuration中描述的导入spring-aopschema。请参阅Section 40.2.7, “the aop schema”了解如何在命名空间中导入aop命名空间。
在您的 Spring 配置中,所有aspect和advisor元素都必须放在一个<aop:config>元素中(您可以在应用程序上下文配置中拥有多个<aop:config>元素)。一个<aop:config>元素可以包含pointcut、advisor和aspect元素(注意这些必须按顺序声明)。
配置
<aop:config>风格大量使用了 Spring 的 自动代理机制。如果您已经通过使用BeanNameAutoProxyCreator或类似的方式使用显式自动代理,这可能会导致问题(例如未编织advice)。推荐的使用模式是要么只使用<aop:config>样式,要么只使用AutoProxyCreator样式。
笔者小结
本章主要介绍通过XML来配置和使用aop,在XML中使用aop需要import spring-aopschema,如下。aspect相关的定义都放在<aop:config>元素(可以有多个)中。
<?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:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd" />
xmlns:aop:当前xml使用aop需要遵循的规范 xmlns:全名是xml namespace,也即是为当前的这个xml指定命名空间。 xsi:schemaLocation:指定的命名空间对应的验证文件,用来定义xml schema的地址,也就是xml书写时需要遵循的语法,用于声明了目标命名空间的模式文档。
声明一个aspect
使用schema支持,aspect只是在 Spring 应用程序上下文中定义为 bean 的常规 Java 对象。状态和行为被捕获在对象的字段和方法中,切入点和advice信息被捕获在 XML 中。
使用 aop:aspect 元素声明aspect,并使用ref属性引用支持 bean:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
...
</aop:aspect>
</aop:config>
<bean id="aBean" class="...">
...
</bean>
支持aspect的 bean(在本例中为“aBean”)当然可以像任何其他 Spring bean 一样进行配置和依赖注入。
声明一个pointcut
可以在 aop:config 元素内声明命名切入点,从而使切入点定义能够在多个aspects和advisors之间共享。
表示服务层中任何业务服务的执行的切入点可以定义如下:
<aop:config>
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
</aop:config>
请注意,切入点表达式本身使用与第 10.2 节“@AspectJ 支持”中描述的相同的 AspectJ 切入点表达式语言。如果您使用基于scheama的声明样式,您可以在切入点表达式中引用类型 (@Aspects) 中定义的命名切入点。定义上述切入点的另一种方法是:
<aop:config>
<aop:pointcut id="businessService"
expression="com.xyz.myapp.SystemArchitecture.businessService()"/>
</aop:config>
假设您有在“共享通用切入点定义”一节中描述的SystemArchitecture aspect。
在aspect内声明切入点与声明顶级切入点非常相似:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..))"/>
...
</aop:aspect>
</aop:config>
与@AspectJ aspect非常相似,使用基于schema的定义样式声明的切入点可以收集连接点上下文。例如,以下切入点收集“this”对象作为连接点上下文并将其传递给advice:
<aop:config>
<aop:aspect id="myAspect" ref="aBean">
<aop:pointcut id="businessService"
expression="execution(* com.xyz.myapp.service.*.*(..)) && this(service)"/>
<aop:before pointcut-ref="businessService" method="monitor"/>
...
</aop:aspect>
</aop:config>
必须通过包含匹配名称的参数来声明advice以接收收集的连接点上下文:
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 引用,不能用作命名切入点来形成复合切入点。因此,基于schema的定义样式中的命名切入点支持比@AspectJ 样式提供的支持更有限。
笔者小结
由于目前很少用XML格式来配置了,所以笔者不在继续赘述,感兴趣的查看官方文档。
选择使用那种AOP声明风格
一旦你决定一个切面是实现给定需求的最佳方法,你如何决定是使用 Spring AOP 还是 AspectJ,以及在 Aspect 语言(代码)风格、@AspectJ 注释风格还是 Spring XML 风格之间做出决定?这些决策受到许多因素的影响,包括应用程序需求、开发工具和团队对 AOP 的熟悉程度。
Spring AOP还是完整的AspectJ?
使用可以工作的最简单的东西。Spring AOP 比使用完整的 AspectJ 更简单,因为不需要将 AspectJ 编译器/编织器引入您的开发和构建过程。如果您只需要advise对 Spring bean 执行操作,那么 Spring AOP 是正确的选择。如果您需要advise不受 Spring 容器管理的对象(例如通常的域对象),那么您将需要使用 AspectJ。如果您希望advise连接点而不是简单的方法执行(例如,字段获取或设置连接点等),您还需要使用 AspectJ。
使用 AspectJ 时,您可以选择 AspectJ 语言语法(也称为“代码样式”)或 @AspectJ 注解样式。显然,如果您不使用 Java 5+,那么已经为您做出了选择……使用代码风格。如果方面在您的设计中扮演重要角色,并且您能够使用 Eclipse 的AspectJ 开发工具 (AJDT)插件,那么 AspectJ 语言语法是首选选项:它更简洁,因为该语言是专门为编写而设计的aspects。如果您没有使用 Eclipse,或者只有几个aspects在您的应用程序中没有发挥主要作用,那么您可能需要考虑使用 @AspectJ 样式并在您的 IDE 中坚持常规 Java 编译,并添加一个aspect编织阶段到您的构建脚本。
笔者小结
如果你只要对方法进行切面并且代理对象是Spring bean那么Spring AOP将是很好的选择。如果你要对域对象进行代理或对代理对象不是简单的方法执行包装你可以选择AspectJ。
Spring AOP使用@AspectJ还是XML
如果您选择使用 Spring AOP,那么您可以选择 @AspectJ 或 XML 样式。有各种权衡需要考虑。
现有的 Spring 用户最熟悉 XML 样式,它由真正的 POJO 支持。当使用 AOP 作为配置企业服务的工具时,XML 可能是一个不错的选择(一个很好的测试是您是否将切入点表达式视为您可能想要独立更改的配置的一部分)。可以说,使用 XML 样式,从您的配置中可以更清楚地了解系统中存在哪些方面。
XML 样式有两个缺点。首先,它没有将它所解决的需求的实现完全封装在一个地方。DRY(Don't Repeat Yourself) 原则说,系统内的任何知识都应该有一个单一的、明确的、权威的表示。当使用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"
表达式= "execution(* get*())" />
<aop:pointcut id = "operationReturningAnAccount"
表达式= "execution(org.xyz.Account+ *(..))" />
XML 方法的缺点是您不能通过组合这些定义来定义“accountPropertyAccess”切入点。
@AspectJ 样式支持额外的实例化模型和更丰富的切入点组合。它具有将方面保持为模块化单元的优点。它还有一个优点,即 Spring AOP 和 AspectJ 都可以理解(并因此使用)@AspectJ aspects - 因此,如果您后来决定需要 AspectJ 的功能来实现其他要求,那么迁移到基于AspectJ方式是非常容易的。总而言之,只要aspects的工作不仅仅是企业服务的简单“配置”,Spring 团队就更喜欢@AspectJ 风格。
笔者小结
Spring团队建议你使用@AspectJ方式来实现切面。因为:
- XML的实现和配置分离了,并且所有切面都封装在一个地方不利于模块化
- XML只能让切面是个单例并且不能组合使用pointcut。
其实这里的实现和配置分离、不利于模块化是目前不使用XML进行配置而使用类进行配置的大部分原因。
混合aspect类型
在相同的配置中,完全可以使用自动代理支持、模式定义的aop:aspect aspect、aop:advisor声明的advisors,甚至使用Spring 1.2风格定义的代理和拦截器,来混合@AspectJ风格的aspects。所有这些都是使用相同的底层支持机制实现的,并且将毫无困难地共存。
代理机制
Spring AOP 使用 JDK 动态代理或 CGLIB 为给定的目标对象创建代理。(当您有选择时,首选 JDK 动态代理)。
如果要代理的目标对象至少实现一个接口,则将使用 JDK 动态代理。目标类型实现的所有接口都将被代理。目标类型实现的所有接口都将被代理。如果目标对象没有实现任何接口,那么将创建一个 CGLIB 代理。
如果您想强制使用 CGLIB 代理(例如,代理为目标对象定义的每个方法,而不仅仅是那些由其接口实现的方法),您可以这样做。但是,有一些问题需要考虑:
final方法不能被advised,因为它们不能被覆盖。- 从 Spring 3.2 开始,不再需要将 CGLIB 添加到项目类路径中,因为 CGLIB 类在 org.springframework 下重新打包并直接包含在 spring-core JAR 中。这意味着基于 CGLIB 的代理支持以与 JDK 动态代理始终具有的相同方式“正常工作”。
- 从 Spring 4.0 开始,代理对象的构造函数将不再被调用两次,因为 CGLIB 代理实例将通过 Objenesis 创建。仅当您的 JVM 不允许绕过构造函数时,您可能会看到来自 Spring 的 AOP 支持的双重调用和相应的调试日志条目。
要强制使用 CGLIB 代理,请将元素proxy-target-class的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 代理。
笔者小结
Spring AOP支持JDK动态代理和CGLIB代理,根据需要动态切换,需要注意的是fianl方法是不会被代理的。强制CGLIB代理可以设置'proxy-target-class'属性值为true,注解也是一样的,注意这个值会被覆盖(会选择更更强的)。
import lombok.SneakyThrows;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@SpringBootApplication
@EnableTransactionManagement(proxyTargetClass = false)
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class Application {
@SneakyThrows
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
了解AOP代理
Spring AOP 是基于代理的。在编写自己的方面或使用 Spring Framework 提供的任何基于 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(..)内部有一个代理引用。这意味着对该对象引用的方法调用将是对代理的调用,因此代理将能够委托给与该特定方法调用相关的所有拦截器(advice)。但是,一旦调用最终到达目标对象,在该在例子中SimplePojo 引用,它可能对自身进行的任何方法调用,例如 this.bar()或者 this.foo(),都将针对this引用而不是代理调用。这具有重要意义。这意味着自调用 不是将导致与方法调用相关的advice有机会执行。
好的,那该怎么办呢?最好的方法(这里使用的术语最好)是重构你的代码,这样自调用就不会发生。当然,这确实需要您做一些工作,但它是最好的、侵入性最小的方法。下一种方法绝对是可怕的,我几乎不愿指出它,因为它太可怕了。您可以(噎住!)通过执行以下操作将类中的逻辑完全绑定到 Spring AOP:
ublic class SimplePojo implements Pojo {
public void foo() {
// this works, but... gah!
((Pojo) AopContext.currentProxy()).bar();
}
public void bar() {
// some logic...
}
}
这完全将您的代码与 Spring AOP 耦合在一起,并且它使类本身意识到它是在 AOP 上下文中使用的,而 AOP 上下文与 AOP 相悖。创建代理时还需要一些额外的配置:
public class Main {
public static void main(String[] args) {
ProxyFactory factory = new ProxyFactory(new SimplePojo());
factory.adddInterface(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 框架。
笔者小结
代理就是执行方法的时候会走代理的方法,代理方法内部调用被代理的方法,被代理内部方法调用和没有代理保持一样。
以编程方式创建@Aspect代理
除了在你的配置中使用<aop:config>或者<aop:aspectj-autoproxy>声明aspects之外,还可以通过编程方式创建advise目标对象的代理。有关 Spring 的 AOP API 的完整详细信息,请参阅下一章。在这里,我们要关注使用@AspectJ aspects自动创建代理的能力。
org.springframework.aop.aspectj.annotation.AspectJProxyFactory 类可以被使用为目标对象(被advised由一个或者多个@AspectJ aspects)创建代理。这个类的基本用法很简单,如下图所示。有关完整信息,请参阅 javadocs。
// 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();
笔者小结
了解下就好,一般不使用。直接在@Aspect注解的类上使用@Component保证能扫描的到就行。
在Spring应用中使用AspectJ
到目前为止,我们在本章中介绍的所有内容都是纯 Spring AOP。在本节中,如果您的需求超出了 Spring AOP 单独提供的功能,我们将研究如何使用 AspectJ 编译器/编织器来代替 Spring AOP 或作为 Spring AOP 的补充。
Spring 附带了一个小的 AspectJ aspect库,它可以在您的发行版中独立使用spring-aspects.jar:您需要将其添加到您的类路径中才能使用其中的aspects。第 10.8.1 节,“使用 AspectJ 通过 Spring 依赖注入域对象”和第 10.8.2 节,“AspectJ 的其他 Spring aspects”讨论了这个库的内容以及如何使用它。第 10.8.3 节,“使用 Spring IoC 配置 AspectJ aspects”讨论了如何依赖注入AspectJ aspects(使用 AspectJ 编译器编织)。最后, 第 10.8.4 节“在 Spring 框架中使用 AspectJ 进行加载时编织”介绍了使用 AspectJ 的 Spring 应用程序的加载时编织。
笔者小结
本章主要讲你的需求超出了Spring AOP单独提供的功能,如何在Spring中使用AspectJ。
使用 AspectJ 通过 Spring 依赖注入域对象
在你的应用程序上下文中Spring 容器实例化和配置定义的bean。根据包含要应用的配置的bean定义的名称,也可以要求bean工厂配置预先存在的对象。spring-aspects.jar包含注解驱动aspect,它利用这个功能允许任何对象的依赖注入。该支持旨在用于在任何容器控制之外创建的对象。域对象通常属于这一类,因为它们通常是使用 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定义(通常为原型范围)配置带注解类型(本例中为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_TYPE)分别根据类型或者名称自动注入。作为替代方案,从 Spring 2.5 开始,最好为@Configurablebeans注解驱动依赖注入,在字段或者方法上(有关更多详细信息,请参见第 6.9 节,“基于注释的容器配置” )使用@Autowired 或者 @Inject显示指定。
最后,您可以使用dependencyCheck属性(例如:@Configurable(autowire=autowire.by_NAME,dependencyCheck=true))为新创建和配置的对象中的对象引用启用Spring依赖项检查。如果此属性设置为 true,则 Spring 将在配置后验证所有属性(不是原类型或集合)都已设置。
当然,单独使用注解什么也没做。是spring-aspects.jar中的AnnotationBeanConfigurerAspect对注解的存在起作用。本质上,aspect说“在从带有注释@Configurable的新对象初始化返回后,根据注解的属性使用 Spring 配置新创建的对象”。在此上下文中, 初始化指的是新实例化的对象(例如,使用“new”运算符实例化的对象)以及Serializable正在进行反序列化的对象(例如,通过 readResolve())。
上一段中的关键词之一是“本质上”。在大多数情况下,“从新对象的初始化返回后”的确切语义将很好…在这种情况下,“初始化后”意味着将在构造对象后注入依赖项。这意味着依赖项在类的构造函数体中不可用。如果您希望在构造函数主体执行之前注入依赖项,从而可以在构造函数主体中使用,那么您需要在
@Configurable声明中定义它,如下所示:
@Configurable(preConstruction=true)您可以在AspectJ Programming Guide的这个附录中找到有关 AspectJ 中各种切入点类型的语言语义的更多信息 。
为此,必须使用AspectJ weaver编织带注解的类型-你可以使用一个Ant或者Maven构建时任务来执行此操作(例如参见 AspectJ 开发环境指南)或者加载时编制(参阅Section 10.8.4, “Load-time weaving with AspectJ in the Spring Framework”)。AnnotationBeanConfigurerAspect本身需要通过Spring 进行配置(以获得对将用于配置新对象的 bean 工厂的引用)。如果您使用基于 Java 的配置,只需添加@EnableSpringConfigured到任何 @Configuration类。
@Configuration
@EnableSpringConfigured
public class AppConfig {
}
如果您更喜欢基于 XML 的配置,Springcontext命名空间定义了一个方便的context:spring-configured元素:
<context:spring-configured/>
在配置aspect之前创建的@Configurable objects实例将导致向调试日志发出消息,并且不会对该对象进行配置。例如,Spring配置中的一个bean在Spring初始化时创建域对象。在这种情况下,您可以使用“depends-on”bean属性手动指定bean依赖于配置aspect。
<bean id="myService"
class="com.xzy.myapp.service.MyService"
depends-on="org.springframework.beans.factory.aspectj.AnnotationBeanConfigurerAspect">
<!-- ... -->
</bean>
不要通过bean 配置器aspect激活@Configurable processing,除非你真的想在运行时依赖它的语义。特别是,请确保不要使用
@Configurable在容器中注册为常规 Spring bean 的 bean 类上:否则,您将获得双重初始化,一次通过容器,一次通过aspect。
笔者小结 就是如果不在Spring容器里的对象需要注入bean时可以使用AspectJ通过Spring依赖注入域对象。下面给出一个简单的例子:
新建的项目,再使用maven插件进行编译即可。
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.1</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- aspects 编译需要的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<!-- <version>${spring.version}</version>-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<!-- <version>${spring.version}</version>-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<!-- <version>${spring.version}</version>-->
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.cache</groupId>
<artifactId>cache-api</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
<scope>provided</scope>
</dependency>
<!-- aspects 编译需要的依赖 end-->
</dependencies>
<repositories>
<repository>
<id>alimaven</id>
<url>https://maven.aliyun.com/repository/public</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>alimaven</id>
<url>https://maven.aliyun.com/repository/public</url>
</pluginRepository>
</pluginRepositories>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>aspectj-maven-plugin</artifactId>
<version>1.7</version>
<configuration>
<complianceLevel>1.8</complianceLevel>
<source>1.8</source>
<target>1.8</target>
<outxml>true</outxml>
<verbose>true</verbose>
<showWeaveInfo>true</showWeaveInfo>
<aspectLibraries>
<aspectLibrary>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
</aspectLibrary>
</aspectLibraries>
</configuration>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
域对象
package com.example.demo.conf;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Configurable;
import org.springframework.context.ApplicationContext;
import java.util.Arrays;
@Configurable
public class User {
@Autowired
ApplicationContext applicationContext;
private String name;
private int age;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String toString(){
return Arrays.asList(applicationContext.getBeanDefinitionNames()).toString();
}
}
测试
package com.example.demo;
import com.example.demo.conf.User;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.aspectj.EnableSpringConfigured;
@SpringBootApplication
@EnableSpringConfigured
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
User user=new User();
System.out.println(user.toString());
}
}
其他章节
其他章节笔者在这里就不继续了感兴趣的读者可以继续阅读,一般的需求使用Spring AOP就足够了,第一节跟下的目的是打开一扇门需要的时候在来取。
更多资源
有关 AspectJ 的更多信息,请访问 AspectJ 网站。
Adrian Colyer等人(Addison Wesley,2005)的书《Eclipse AspectJ》为AspectJ语言提供了全面的介绍和参考。
Ramnivas Laddad(Manning,2009 年)的《 AspectJ in Action, Second Edition 》一书受到了强烈推荐;本书的重点是AspectJ,但也探讨了很多通用的 AOP 主题(在一定程度上)。
回顾目标
-
aop的机制是什么,具体怎么实现的? 具体怎么实现的后面会出相关aop源码阅读文章,敬请期待。 -
什么时候用jdk动态代理?不再赘述,前文有提到。 -
什么时候用cglib代理?不再赘述,前文有提到。 -
jdk代理和cglib代理的区别是啥? 不再赘述,前文有提到。
-
jdk代理一定比cglib代理慢吗? 后续会出比较的文章,敬请期待。
- Spring AOP和AspectJ有什么关联?不再赘述,前文有提到。 -
Spring AOP应用了那些设计模式? 会和aop源码阅读文章一起提及,敬请期待。