一、AOP 概述
1️⃣什么是AOP
面向切面编程(AOP)通过提供另一种考虑程序结构的方法对面向对象编程(OOP)进行了补充。
OOP中模块化的关键单元是类,而AOP中模块化的关键单元是aspect(切面)。
Spring的关键组件之一是AOP框架。 虽然Spring IoC容器不依赖于AOP(这意味着如果您不想使用AOP就不需要),但AOP对Spring IoC进行了补充,提供了一个非常强大的企业级解决方案。
这里有几个名词需要了解一下:
aop alliance:是AOP联盟,该组织定义了很多针对面向切面的接口api,通常Spring等其它具备动态织入功能的框架依赖此包。
AspectJ:AOP虽然是方法论,但就好像OOP中的Java一样,一些先行者也开发了一套语言来支持AOP。目前用得比较火的就是AspectJ语言了,它是一种几乎和Java完全一样的语言,而且完全兼容Java。当然spring也有独立的AOP的实现。
2️⃣AOP相关术语
Aspect(切面): 一个关注点的模块化,这个关注点可能会横切多个对象。事务管理是J2EE应用中一个关于横切关注点的很好的例子。
Join point(连接点 ): 在程序执行过程中某个特定的点,比如某方法调用的时候或者处理异常的时候。
Advice(通知): 在切面的某个特定的连接点(Joinpoint)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。通知的类型将在后面部分进行讨论。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。
Pointcut(切入点 ): 匹配连接点(Joinpoint)的断言。通知和一个【切入点表】达式关联,并在满足这个切入点的连接点上运行。【切入点表达式如何和连接点匹配】是AOP的核心:Spring缺省使用AspectJ切入点语法。
Introduction(引入): Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。
Target object(目标对象): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做被通知(advised) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
AOP代理 AOP proxy: 在Spring中,AOP代理可以是JDK动态代理或者CGLIB代理。
Weaving(织入): 把切面(aspect)连接到其它的应用程序类型或者对象上,并创建一个被通知(advised)的对象,这个过程叫织入。这些可以在编译时(例如使用AspectJ编译器),类加载时和运行时完成。 Spring和其他纯Java AOP框架一样,在运行时完成织入。
3️⃣Spring AOP通知类型
Before advice : 在连接点之前运行的通知,但不能阻止执行流继续执行到连接点(除非它抛出异常)。
After returning advice : 在连接点正常完成后运行的通知(例如,如果方法返回而不引发异常)。
After throwing advice: 在方法通过抛出异常退出时运行的通知。
After (finally) advice: 不管连接点以何种方式退出(正常或异常返回),都要运行的通知。
Around advice: 围绕连接点(如方法调用)的通知。 这是最有力的建议。 Around通知可以在方法调用前后执行自定义行为。它还负责选择是继续到连接点,还是通过返回自己的返回值或抛出异常来简化被通知的方法执行。
二、Spring AOP能力和目标
Spring AOP是用纯Java实现的。 不需要特殊的编译过程。
Spring AOP目前只支持【方法执行连接点】(在Spring bean上的方法上执行通知)。
如果需要通知字段访问和更新连接点,可以考虑使用AspectJ之类的语言。
Spring AOP的AOP方法不同于大多数其他AOP框架。 目的不是提供最完整的AOP实现(尽管Spring AOP很有能力)。
相反,其目的是提供AOP实现和Spring IoC之间的紧密集成,以帮助解决企业应用程序中的常见问题。
Spring和AspectJ
Spring框架的AOP功能通常与Spring IoC容器一起使用。 切面是通过使用普通beanDifination语法配置的。
使用Spring AOP不能轻松或有效地完成一些事情,比如通知非常细粒度的对象(通常是域对象)。 AspectJ是这种情况下的最佳选择。然而,我们的经验是,Spring AOP为企业Java应用程序中的大多数问题提供了一个很好的解决方案。
Spring AOP从不与AspectJ竞争,以提供全面的AOP解决方案。 我们相信基于代理的框架(如Spring AOP)和成熟的框架(如AspectJ)都是有价值的,它们是互补的,而不是相互竞争的。 Spring无缝地将Spring AOP和IoC与AspectJ集成在一起,以支持在一致的基于Spring的应用程序体系结构中使用AOP。 这种集成不会影响Spring AOP API或AOP Alliance API, Spring AOP保持向后兼容。
三、AOP代理
Spring AOP默认为AOP代理使用标准的JDK动态代理, 这允许代理任何接口(或接口集)。
Spring AOP也可以使用CGLIB代理。 缺省情况下,如果业务对象没有实现接口,则使用CGLIB。
由于编写接口是很好的实践,因此业务类通常实现一个或多个业务接口是可能的。
四、@AspectJ风格的支持
@AspectJ是将【切面】声明为带有注解的常规Java类的一种风格。 @AspectJ风格是由AspectJ项目作为AspectJ 5发行版的一部分引入的。 Spring与AspectJ 5有相同的注解, 但是,AOP运行时仍然是纯Spring AOP,并且不依赖于AspectJ编译器或编织器。
1️⃣对于 @AspectJ的支持
要在Spring配置中使用@AspectJ注解,您需要启用Spring支持,以便基于@AspectJ注解配置Spring AOP,如果Spring确定一个bean被一个或多个切面通知,它将自动为该bean生成一个代理,以拦截方法调用,并确保通知在需要时运行。
@AspectJ支持可以通过XML或java的配置来启用。 在这两种情况下,你还需要确保【AspectJ的’ aspectjweaver.jar ‘库】在你的应用程序的类路径上(1.8或更高版本)。 这个库可以在AspectJ发行版的’ lib '目录中或Maven中央存储库中获得。
🍀使用Java配置启用@AspectJ支持
要使用Java的【@Configuration】启用@AspectJ支持,请添加【@EnableAspectJAutoProxy】注解,如下面的示例所示:
@Configuration @EnableAspectJAutoProxy public class AppConfig { }
🍀使用XML配置启用@AspectJ支持,请使用<aop:aspectj-autoproxy/>元素,如下例所示:
<?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" 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"> <!-- bean definitions here --> <aop:aspectj-autoproxy/> </beans>
当然,我们需要引入aop的命名空间。
2️⃣声明一个切面
启用@AspectJ支持后,在应用程序上下文中定义的任何带有@AspectJ注解类的bean都会被Spring自动检测并用于配置Spring AOP。
两个示例中的第一个展示了应用程序上下文中的常规beanDifination,它指向一个具有“@Aspect”注解的bean类:
<bean id="myAspect" class="org.xyz.NotVeryUsefulAspect"> <!-- configure properties of the aspect here --> </bean>
两个示例中的第二个展示了’ NotVeryUsefulAspect ‘类定义,它是用’ org.aspectj.lang.annotation '标注的。 方面的注解;
package org.xyz; import org.aspectj.lang.annotation.Aspect; @Aspect public class NotVeryUsefulAspect { }
用“@Aspect”标注的类可以有方法和字段,与任何其他类一样。 它们还可以包含切入点、通知和引入(类型间)声明。
通过组件扫描自动检测切面你可以在Spring XML配置中通过“@Configuration”类中的“@Bean”方法将切面类注册为常规bean,或者让Spring通过类路径扫描自动检测它们——就像任何其他Spring管理的bean一样。 但是,请注意,“@Aspect”注解不足以实现类路径中的自动检测。 为了达到这个目的,您需要添加一个单独的【@Component】注解。
在Spring AOP中,切面本身不能成为来自其他通知的目标。 类上的“@Aspect”注解将其标记为一个切面类,因此会将其排除在自动代理之外。
3️⃣声明一个切入点
【切入点确定感兴趣的连接点】,从而使我们能够控制通知何时运行。
切入点声明由两部分组成:包含【名称和方法签名】,以及确定我们感兴趣的方法执行的【切入点表达式】。
怎么确定一个方法:public void com.ydlclass.service.impl.*(…)
@Pointcut("execution(* transfer(..))") // the pointcut expression private void anyOldTransfer() {} // the pointcut signature
🍀支持切入点指示器
Spring AOP支持以下在切入点表达式中使用的AspectJ切入点指示器(PCD):
- execution:(常用)用于匹配方法执行的连接点,这是在使用Spring AOP时使用的主要切入点指示符。(匹配方法)
within: 用于匹配指定类型内的方法执行。(匹配整个类)
this: 用于匹配当前【AOP代理对象】类型的执行方法;注意是AOP代理对象的类型匹配,这样就可能【包括引入接口】也进行类型匹配。(配置整个类)
target: 用于匹配当前目标对象类型的执行方法;注意是目标对象的类型匹配,这样就【不包括引入接口】也进行类型匹配。(配置整个类)
args: 限制匹配连接点(使用Spring AOP时的方法执行),其中参数是给定类型的实例。 (参数类型匹配)
@target: 用于匹配当前目标对象类型的执行方法,其中目标对象持有指定的注解 。(类上的注解)
@args: 用于匹配当前执行的方法传入的参数持有指定注解的执行。(参数上的注解)
@within: 用于匹配所有持有指定注解类型内的方法。(类上的注解)
@annotation: (常用)于匹配当前执行方法持有指定注解的方法。(方法上的注解)
bean:使用“bean(Bean id或名字通配符)”匹配特定名称的Bean对象的执行方法;Spring ASP扩展的,在AspectJ中无相应概念。