AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 AOP 是 Spring 框架的关键组件之一。虽然 Spring IoC 容器不依赖于 AOP,但在 Spring 应用中,经常会使用 AOP 来简化编程。在 Spring 框架中使用 AOP 主要有以下优势: ♞ 提供声明式企业服务,特别是作为 EJB 声明式服务的替代品。最重要的是,这种服务是声明式事务管理。 ♞ 允许用户实现自定义切面。在某些不适合用 OOP 编程的场景中,采用 AOP 来补充。 ♞ 可以对业务逻辑的各个部分进行隔离,从而使业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。 要使用 Spring AOP 需要添加 spring-aop 模块。
♞ Join Point(连接点)
:连接点是指那些被拦截到的点。在 Spring 中,这些点指的是方法,因为 Spring 只支持方法类型的连接点
♞ Advice(通知)
:通知是指拦截到 Join Point 之后所要做的事情就是通知,通知有各种类型,其中包括 around、before 和 after 等通知。许多 AOP 框架,包括 Spring,都是以拦截器来实现通知模型的,并维护一个以连接点为中心的拦截器链。
♞ Pointcut(切入点)
:切入点是指我们要对哪些 Join Point 进行拦截的定义。通知和一个切入点表达式关联,并在满足这个切人点的连接点上运行(如当执行某个特定名称的方法时)。切入点表达式如何和连接点匹配是 AOP 的核心。Spring 默认使用 AspectJ 切入点语法。
♞ Aspect(切面)
:是切入点和通知的结合
♞ Introduction(引入)
:声明额外的方法或某个类型的字段。Spring 允许引入新的接口(及一个对应的实现)到任何被通知的对象。例如,可以使用一个引入来使 Bean 实现 IsModified 接口,以便简化缓存机制。在 AspectJ 社区,Introduction 也被称为 Inter-type Declaration(内部类型声明)。
♞ Target Object(目标对象)
:被一个或多个切面所通知的对象。也有人把它称为 Advised(被通知)对象。既然 Spring AOP 是通过运行时代理实现的,那这个对象永远是一个 Proxied(被代理)对象。
♞ AOP Proxy(AOP代理)
:AOP 框架创建的对象,用来实现 Aspect Contract(切面契约)包括通知方法执行等功能。在 Spring 中,AOP 代理可以是 JDK 动态代理或 CGLIB 代理。
♞ Weaving(织入)
:把切面应用到目标对象来创建新的代理对象的过程。Spring 采用动态代理织入,而 AspectJ 采用编译期织入和类装载期织入
Spring AOP 用纯 Java 实现,它不需要专门的编译过程。Spring AOP 不需要控制类装载器层次,因此它适用于 Servlet 容器或应用服务器。Spring 目前仅支持方法调用作为连接点之用。虽然可以在不影响 Spring AOP 核心 API 的情况下加入对成员变量拦截器的支持,但 Spring 并没有实现成员变量拦截器。Spring 与其他纯 Java AOP 框架一样,在运行时完成织入。其中有关 Advice(通知)的类型主要有以下几种:
♞ Before Advice(前置通知)
:在某连接点之前执行的通知,但这个通知不能阻止连接点前的执行,除非它抛出一个异常。
♞ After Returning Advice(返回后通知)
:在某连接点正常完成后执行的通知,如果一个方法没有抛出异常,将正常返回。
♞ After Throwing Advice(抛出异常后通知)
:在方法抛出异常退出时执行的通知。
♞ After Finally Advice(最后通知)
:当某连接点退出时执行的通知,不论是正常返回还是异常退出。
♞ Around Advice(环绕通知)
:环绕一个连接点的通知。这是最强大的一种通知类型。环绕通知可以在方法调用前后完成自定义的行为,它也会选择是否继续执行连接点,或者直接返回它们自己的返回值或抛出异常来结束执行。
[☞ Spring AOP 的动态代理技术]
<!-- spring-context 依赖于 spring-aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
<!-- aspectj 织入 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.6</version>
</dependency>
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/17
* @description 目标接口
*/
public interface Run {
void run();
}
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/17
* @description 目标类
*/
public class RunImpl implements Run {
public void run() {
System.out.println("我是目标类");
}
}
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/17
* @description 切面类
*/
public class MyAspect {
// 前置通知
public void before(){
System.out.println("前置通知");
}
}
<bean class="com.softrware.spring.targetObject.Run" id="run"/>
<bean class="com.softrware.spring.aspect.MyAspect" id="myAspect"/>
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
<!-- AOP 配置-->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 配置切点,关联前置通知和连接点,expression 配置切点表达式 -->
<aop:before method="before"
pointcut="execution(public void com.softrware.spring.targetObject.impl.RunImpl.run())"/>
</aop:aspect>
</aop:config>
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/17
* @description 测试类
*/
public class Demo {
@Test
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("application.xml");
// 此处一定要使用接口,代理只能对接口代理无法对实现类进行代理
Run run = (Run) applicationContext.getBean("runImpl");
run.run();
}
}
☞ 语法
execution([修饰符] 返回值类型 包名.类名.方法名(参数))
☞ 说明
♞ 访问修饰符可以省略
♞ 返回值类型、包名、类名、方法名可以使用星号 *
代表任意
♞ 包名与类名之间一个点 .
代表当前包下的类,两个点 ..
表示当前包及其子包下的类
♞ 参数列表可以使用两个点 ..
表示任意个数,任意类型的参数列表
☞ 示例
// 指定方法
execution(public void com.softrware.spring.targetObject.impl.RunImpl.run()())
// 省略 public,Run 实现类中的所有方法
execution(void com.softrware.spring.targetObject.impl.RunImpl.*(..))
// 省略修饰符、任意返回值,impl 包下所有类的所有方法
execution(* com.softrware.spring.targetObject.impl.*.*(..))
// 省略修饰符、任意返回值,targetObject 及其子包下所有类的所有方法
execution(* com.softrware.spring.targetObject..*.*(..))
// 省略修饰符、任意返回值,任意包下任意方法
execution(* *..*.*(..))
☞ 语法
<aop:config>
<aop:aspect ref="切面类">
<aop:before method="通知方法名称" pointcut="切点表达式"></aop:before>
</aop:aspect>
</aop:config>
☞ 常用通知
☞ 示例
<!-- AOP 配置-->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 抽取切点表达式 -->
<aop:pointcut id="runPointCut"
expression="execution(public * com.softrware.spring.targetObject.impl.RunImpl.run())"/>
<!-- 配置切点,关联通知和连接点 -->
<aop:around method="around" pointcut-ref="runPointCut"/>
<aop:before method="before" pointcut-ref="runPointCut"/>
<aop:after method="after" pointcut-ref="runPointCut"/>
</aop:aspect>
</aop:config>
/**
* Created with IntelliJ IDEA.
*
* @author Demo_Null
* @date 2020/8/17
* @description 切面类
*/
public class MyAspect {
// 前置通知
public void before(){
System.out.println("前置通知");
}
public void after() {
System.out.println("后置通知");
}
// 需要接收 ProceedingJoinPoint 进行相关操作
public void around(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("环绕通知1");
pjp.proceed();
System.out.println("环绕通知2");
}
}
当多个增强的切点表达式相同时,可以将切点表达式进行抽取,在增强中使用 pointcut-ref 属性代替 pointcut 属性来引用抽取后的切点表达式。
<!-- AOP 配置-->
<aop:config>
<!-- 配置切面 -->
<aop:aspect ref="myAspect">
<!-- 抽取切点表达式 -->
<aop:pointcut id="runPointCut"
expression="execution(public void com.softrware.spring.targetObject.impl.RunImpl.run())"/>
<!-- 配置切点,关联通知和连接点 -->
<aop:before method="before" pointcut-ref="runPointCut"/>
</aop:aspect>
</aop:config>