文章目录
- 深度解析 Spring 源码:解密AOP切点和通知的实现机制
- 一、Spring AOP的基础知识
- 1.1 AOP的核心概念:切点、通知、切面等
- 1.2 Spring AOP与传统AOP的区别和优势
- 二、深入分析切点和通知的实现
- 2.1 研究 Pointcut 接口及其实现类
- 2.1.1 Pointcut 接口
- 2.1.2 AspectJExpressionPointcut类
- 2.1.3 NameMatchMethodPointcut类
- 2.2 探讨 Advice 接口及其实现类
- 2.2.1 通知类型
- 2.2.2 MethodBeforeAdvice接口
- 2.2.3 AspectJMethodBeforeAdvice类
- 三、实际与运用
- 3.1 代码展示Spring AOP的用法和配置方式
- 3.2 结合实例说明切点和通知如何在实际项目中应用
深度解析 Spring 源码:解密AOP切点和通知的实现机制
一、Spring AOP的基础知识
1.1 AOP的核心概念:切点、通知、切面等
使用AOP可以将那些与核心业务逻辑无关但又分散在各处的横切关注点(如日志记录、性能监控、事务管理等)抽离出来,通过切面的方式进行统一管理和维护,从而提高了代码的模块化程度、可维护性和可扩展性。
- 切点:切点是在应用程序中定义的一组条件,用于确定何处插入横切关注点。在Java中,切点通常是由表达式来定义的,这些表达式可以匹配到程序中的特定方法调用或者其他程序执行的位置。例如,一个切点可以定义为匹配所有service包下的方法调用。切点实际上是AOP在代码中的具体位置。
- 通知:通知是在切点上执行的代码,它定义了在何时、何地以及如何执行横切逻辑。通知可以是在切点之前执行(Before advice)、在切点之后执行(After advice)、在方法返回值后执行(After-returning advice)、在方法抛出异常后执行(After-throwing advice)以及环绕执行(Around advice)等不同类型。
- 切面:切面是横切关注点的模块化实现,它将通知和切点组合在一起。一个切面是一个类,它包含了多个通知和切点的定义。在实际应用中,切面可以被看作是一种特殊的类,它提供了一种方式来定义横切关注点,并将其与主要业务逻辑分离开来。
关系图:
1.2 Spring AOP与传统AOP的区别和优势
Spring AOP相对于传统AOP来说更加轻量级和易用,适合于大部分应用场景下的AOP需求。传统AOP则提供了更丰富的功能和更灵活的配置选项,适用于对AOP功能有更高要求的特定场景。
实现与扩展方面的区别和优势:
- 实现方式:
- 传统AOP:传统的AOP实现通常是通过静态代理或者动态代理来实现的。静态代理要求在编译时就确定代理对象,而动态代理则是在运行时生成代理对象。传统AOP的实现需要程序员手动编写代理类或使用代码生成工具,相对比较繁琐。
- Spring AOP:Spring AOP是基于动态代理实现的,它利用了JDK动态代理和CGLIB动态代理来在运行时生成代理对象。Spring AOP通过配置来定义切点和通知,而无需手动编写代理类,简化了AOP的实现。
- 依赖关系:
- 传统AOP:传统的AOP实现通常依赖于特定的AOP框架,如AspectJ。使用传统AOP需要引入独立的AOP框架,并学习其专门的语法和配置方式。
- Spring AOP:Spring AOP是Spring框架的一部分,与Spring IoC容器紧密集成。因此,使用Spring AOP无需引入额外的依赖,而是直接利用Spring的核心功能实现AOP,简化了项目的依赖管理和配置。
- 功能扩展:
- 传统AOP:传统AOP通常提供了更丰富的功能和更灵活的配置选项,如支持更多类型的通知(如引入通知)、更细粒度的切点定义等。
- Spring AOP:Spring AOP相对于传统AOP来说功能较为简单,只支持方法级别的切面,通知类型也相对较少。但Spring AOP提供了与Spring框架无缝集成的优势,能够与Spring的IoC容器和其他功能(如事务管理、异常处理等)无缝配合,使得AOP的应用更加方便和统一。
二、深入分析切点和通知的实现
2.1 研究 Pointcut 接口及其实现类
2.1.1 Pointcut 接口
接口 Pointcut 表示一个切点,可以确定哪些类和方法应该被包含在一个切面中。通过提供类过滤器和方法匹配器,允许开发者定义更加精确的切点条件。
2.1.2 AspectJExpressionPointcut类
本文仅仅分析用于确定给定方法是否匹配切点条件的matches方法,对于实现类的其它方法,读者感兴趣可自行解读,亦可以期待后续的博文。
第一个 matches
方法位于 ShadowMatch
对象上,用于判断连接点是否匹配切点条件。这个方法是由 ShadowMatch
类提供的,用于执行切点表达式与目标方法的匹配逻辑。
第二个 matches
方法位于 JoinPointMatch
对象上,用于判断连接点的匹配状态。这个方法是由 JoinPointMatch
类提供的,用于判断连接点是否成功匹配了切点表达式。
2.1.3 NameMatchMethodPointcut类
检查给定的方法名是否与列表中的任何一个方法名匹配。
用于判断一个字符串是否符合给定的模式,源码结合切面表达式看易于理解。
/**
* execution(): 这是最常用的切入点函数,在方法执行时触发切入点
* 切入点函数参数: 包括方法的访问修饰符、返回类型、类名、方法名和参数列表等
* 通配符: 例如*用于匹配任意字符,..用于匹配任意数量的参数等
* 逻辑运算符: 例如&&表示与,||表示或,!表示非
*/
@Pointcut("execution(* com.example.service.*.*(..))")
private void serviceMethods() {}
2.2 探讨 Advice 接口及其实现类
Advice
接口有多个子接口,如MethodBeforeAdvice
、AfterReturningAdvice
、ThrowsAdvice
等。本文解读前置通知,其它通知读者感兴趣可以自行去了解。
2.2.1 通知类型
通知类型可以分为以下几种:
- 前置通知(Before Advice):
- 在目标方法执行之前执行的逻辑。
- 实现了
org.springframework.aop.MethodBeforeAdvice
接口的通知称为前置通知。 - 通常用于执行一些准备工作,比如权限检查、日志记录等。
- 后置通知(After Returning Advice):
- 在目标方法成功执行之后执行的逻辑。
- 实现了
org.springframework.aop.AfterReturningAdvice
接口的通知称为后置通知。 - 通常用于处理方法的返回值或清理工作。
- 环绕通知(Around Advice):
- 在目标方法执行前后都能执行的逻辑,可以控制目标方法的执行。
- 实现了
org.aopalliance.intercept.MethodInterceptor
接口的通知称为环绕通知。 - 通常用于包装目标方法的调用,实现额外的逻辑,比如性能监控、事务管理等。
- 抛出异常通知(After Throwing Advice):
- 在目标方法抛出异常后执行的逻辑。
- 实现了
org.springframework.aop.ThrowsAdvice
接口的通知称为抛出异常通知。 - 通常用于异常处理、日志记录等。
- 引介通知(Introduction Advice):
- 在不修改目标类的前提下,为目标类添加新的方法或字段。
- 实现了
org.springframework.aop.IntroductionInterceptor
接口的通知称为引介通知。 - 通常用于向现有类添加新的行为。
2.2.2 MethodBeforeAdvice接口
用于在目标方法执行之前执行某些操作。
2.2.3 AspectJMethodBeforeAdvice类
主要作用是在目标方法执行前执行一些额外的操作。
三、实际与运用
3.1 代码展示Spring AOP的用法和配置方式
Spring AOP是 Spring 框架的一个重要特性,允许以声明性方式来定义横切关注点,如日志记录、性能监控、事务管理等,而无需修改业务逻辑代码。
Spring AOP 的使用XML形式的Demo,包括配置方式和用法:
- 添加依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.9</version> <!-- 版本号可以根据实际情况调整 -->
</dependency>
- 创建切面类
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
/**
* @Aspect 注解表示这是一个切面类
* @Before 注解表示在目标方法执行之前执行通知
* 切入点表达式指定了切入点为 com.example.service 包中的所有类的所有方法
*/
@Aspect
public class LoggingAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore() {
System.out.println("Logging before method execution...");
}
}
- 配置 Spring Bean
<!-- Spring 配置文件中声明切面类为一个 Spring Bean -->
<bean id="loggingAspect" class="com.example.aspect.LoggingAspect"/>
- 启用 Spring AOP
<!-- 在 Spring 配置文件中启用 Spring AOP -->
<aop:aspectj-autoproxy/>
- 创建业务类
package com.example.service;
public class MyService {
public void doSomething() {
System.out.println("Doing something...");
}
}
- 测试
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.example.service.MyService;
public class Main {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
MyService myService = (MyService) context.getBean("myService");
myService.doSomething();
}
}
3.2 结合实例说明切点和通知如何在实际项目中应用
以订单创建日志记录的Demo:
- 定义订单服务接口
public interface OrderService {
void createOrder(Order order);
}
- 实现订单服务
@Service
public class OrderServiceImpl implements OrderService {
@Override
public void createOrder(Order order) {
// 创建订单的具体逻辑
System.out.println("订单已创建:" + order);
}
}
- 创建订单实体类
public class Order {
private Long id;
private String customerName;
private double amount;
// 省略 getter 和 setter 方法
}
- 创建日志切面
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* 在LoggingAspect切面类上添加了@Aspect和@Component注解,用于告诉Spring这是一个切面类,并将其纳入Spring容器管理
*/
@Aspect
@Component
public class LoggingAspect {
/**
* 使用了@Before注解来定义了一个前置通知
* 执行OrderService接口的createOrder方法之前被触发
* 切入点表达式指定切入点为OrderService接口的createOrder方法
*/
@Before("execution(* com.example.service.OrderService.createOrder(..)) && args(order)")
public void logBefore(Order order) {
System.out.println("订单已创建,订单ID:" + order.getId() + ",客户姓名:" + order.getCustomerName());
System.out.println("记录订单创建日志...");
}
}
- AppConfig配置类
/**
* 使用了@Configuration、@ComponentScan和@EnableAspectJAutoProxy注解来启用Spring AOP和组件扫描
*/
@Configuration
@ComponentScan(basePackages = "com.example")
@EnableAspectJAutoProxy
public class AppConfig {
}
- 测试
public class Main {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
OrderService orderService = context.getBean(OrderService.class);
Order order = new Order();
order.setId(1L);
order.setCustomerName("Alice");
order.setAmount(100.0);
orderService.createOrder(order);
}
}
// 输出结果
订单已创建,订单ID:1,客户姓名:Alice
记录订单创建日志...
订单已创建:Order{id=1, customerName='Alice', amount=100.0}
古人学问无遗力,少壮工夫老始成