在本专栏之前的文章中已经带大家熟悉了Spirng中核心概念IOC的原理以及手写了核心代码,接下来将继续介绍Spring中另一核心概念AOP。
AOP即切面编程是Spring框架中的一个关键概念,它允许开发者在应用程序中优雅地处理横切关注点,如日志记录、性能监控和事务管理。在切面编程中,切点表达式是一项关键技术,它定义了在何处应用切面的逻辑。本章将深入探讨Spring切点表达式的实现原理,为读者提供对这一重要概念的深刻理解。
假设我们有一个在线商城的Web应用,用户可以浏览商品、下单购买商品等。我们希望记录每个HTTP请求的开始时间、结束时间以及执行时间,以便监控应用的性能并快速定位潜在的问题。
我们可以使用Spring AOP和切点表达式来实现这个日志记录功能。以下是实现步骤:
1. 创建一个切面类:
- import org.aspectj.lang.JoinPoint;
- import org.aspectj.lang.annotation.After;
- import org.aspectj.lang.annotation.Aspect;
- import org.aspectj.lang.annotation.Before;
- import org.springframework.stereotype.Component;
-
- @Aspect
- @Component
- public class RequestLoggingAspect {
-
- private long startTime;
-
- @Before("execution(* com.example.controller.*.*(..))")
- public void logBefore(JoinPoint joinPoint) {
- startTime = System.currentTimeMillis();
- System.out.println("Request received for: " + joinPoint.getSignature().toShortString());
- }
-
- @After("execution(* com.example.controller.*.*(..))")
- public void logAfter(JoinPoint joinPoint) {
- long endTime = System.currentTimeMillis();
- long executionTime = endTime - startTime;
- System.out.println("Request completed for: " + joinPoint.getSignature().toShortString());
- System.out.println("Execution Time: " + executionTime + "ms");
- }
- }
上述代码定义了一个切面类 RequestLoggingAspect
,其中包含两个通知方法 logBefore
和 logAfter
。logBefore
方法在方法执行前记录开始时间和请求信息,而 logAfter
方法在方法执行后记录结束时间和执行时间。
2. 配置切点表达式:
在切面类中,我们使用 @Before
和 @After
注解分别标注了 logBefore
和 logAfter
方法,并指定了切点表达式 "execution(* com.example.controller.*.*(..))"
。这个切点表达式表示我们希望拦截所有 com.example.controller
包下的方法执行。
3. 启用AspectJ支持:
确保在Spring配置文件中启用了AspectJ的支持。可以通过以下配置实现:
<aop:aspectj-autoproxy />
在Spring Boot应用的配置类中,保证@Aspect标记的切面类正确被容器管理即可。
4. 结果:
当用户发起HTTP请求时,切面会自动拦截匹配的方法,记录请求的开始时间和结束时间,并输出到日志中。这样,我们就能够实时监控每个请求的性能,并在需要时进行故障排除。
在Spring框架中,切点(Pointcut)和连接点(JoinPoint)是实现切面编程的两个核心概念。切点定义了在应用程序中哪些地方切入(或触发)切面的逻辑,而连接点则代表在应用程序执行过程中的具体执行点。连接点可以是方法的调用、方法的执行、异常的抛出等。理解这两个概念是理解切点表达式的基础。
AspectJ是一种强大的面向切面编程(AOP)语言,Spring框架引入了AspectJ切点表达式以方便开发者定义切点。AspectJ切点表达式使用一种类似于正则表达式的语法来匹配连接点。
AspectJ切点表达式的语法包括以下几个关键部分:
- //匹配com.example.service包中的所有类的所有方法
- execution(* com.example.service.*.*(..))
- //匹配任何公共方法的执行。
- execution(public * com.example.service.*.*(..))
- //匹配com.example.service包中UserService类的所有方法执行。
- execution(* com.example.service.UserService.*(..))
- //匹配UserService类中以"get"开头的所有方法执行。
- execution(* com.example.service.UserService.get*(..))
- //匹配UserService类的所有方法执行,无论参数列表如何
- execution(* com.example.service.UserService.*(..))
AspectJ切点表达式的灵活性使开发者能够定义精确的切点,以满足不同的应用需求。通过深入学习和掌握AspectJ切点表达式,开发者可以更好地利用Spring AOP来管理应用程序中的横切关注点。接下来,我们将深入研究切点表达式的实现原理,以更好地理解Spring框架是如何解析和匹配这些表达式的。
ClassFilter 和 MethodMatcher 接口
AspectJExpressionPointcut 的简单实现
下面将借助aspectjweaver的功能简单实现Spring AOP切点表达式功能,实现对execution函数的支持。
- <dependency>
- <groupId>org.aspectjgroupId>
- <artifactId>aspectjweaverartifactId>
- <version>1.8.0version>
- dependency>
特征 | Spring AOP | AspectJ |
---|---|---|
编程模型 | 基于代理的编程模型,使用 Spring 代理生成 AOP 代理。 | 纯粹基于注解或 XML 的编程模型,使用 AspectJ 编译器或运行时织入器。 |
编织方式 | 运行时织入,通过代理包装目标对象来添加切面行为。 | 支持编译时织入和运行时织入,更灵活且功能更强大。 |
性能 | 由于使用代理,性能开销较小,但有些限制。 | 性能较好,编译时织入可以最小化运行时开销。 |
支持的切入点表达式(Pointcut) | 仅支持一部分切入点表达式,如方法执行(execution)。 | 支持广泛的切入点表达式,包括访问、调用、初始化等多种方式。 |
复杂度 | 适用于简单的切面需求,易于配置和使用。 | 适用于复杂的切面需求,提供更多高级功能和灵活性。 |
集成度 | 紧密集成到 Spring 框架中,易于使用和配置。 | 相对独立,需要额外配置 AspectJ 编译器或运行时织入器。 |
配置方式 | 使用 Spring 的注解或 XML 配置来定义切面。 | 使用 AspectJ 注解或 XML 配置来定义切面。 |
- public interface ClassFilter {
- boolean matches(Class> clazz);
- }
- public interface MethodMatcher {
- boolean matches(Method method, Class> targetClass);
- }
- public interface Pointcut {
- ClassFilter getClassFilter();
-
- MethodMatcher getMethodMatcher();
- }
- /**
- * ● @author: YiHui
- * ● @date: Created in 17:33 2023/9/24
- * ● @Description: 这是一个自定义的 AspectJ 表达式切点,用于在 Spring AOP 中匹配切点表达式。
- */
- public class AspectJExpressionPointcut implements Pointcut, ClassFilter, MethodMatcher {
-
- // 支持的切点原语集合
- private static final Set
SUPPORTED_PRIMITIVES = new HashSet<>(); -
- static {
- // 添加支持的切点原语。在此示例中,我们仅支持 EXECUTION 原语,您可以根据需要添加更多。
- SUPPORTED_PRIMITIVES.add(PointcutPrimitive.EXECUTION);
- }
-
- // 切点表达式对象,用于解析和匹配切点
- private final PointcutExpression pointcutExpression;
-
- /**
- * 构造函数,用给定的表达式创建 AspectJ 表达式切点。
- *
- * @param expression 切点表达式,用于定义匹配的切点
- */
- public AspectJExpressionPointcut(String expression) {
- // 创建一个 PointcutParser 实例,用于解析切点表达式
- PointcutParser pointcutParser = PointcutParser.getPointcutParserSupportingSpecifiedPrimitivesAndUsingSpecifiedClassLoaderForResolution(
- SUPPORTED_PRIMITIVES, this.getClass().getClassLoader());
-
- // 解析给定的切点表达式并将其分配给成员变量 pointcutExpression
- pointcutExpression = pointcutParser.parsePointcutExpression(expression);
- }
-
- /**
- * 检查给定的类是否符合切点表达式的条件。
- *
- * @param clazz 要检查的类
- * @return 如果类匹配切点表达式,则返回 true,否则返回 false
- */
- @Override
- public boolean matches(Class> clazz) {
- return pointcutExpression.couldMatchJoinPointsInType(clazz);
- }
-
- /**
- * 检查给定的方法是否符合切点表达式的条件。
- *
- * @param method 要检查的方法
- * @param targetClass 方法所属的目标类
- * @return 如果方法匹配切点表达式,则返回 true,否则返回 false
- */
- @Override
- public boolean matches(Method method, Class> targetClass) {
- // 使用切点表达式检查方法执行是否匹配
- return pointcutExpression.matchesMethodExecution(method).alwaysMatches();
- }
-
- /**
- * 获取用于类筛选的 ClassFilter 实例。
- *
- * @return ClassFilter 实例,用于过滤匹配的类
- */
- @Override
- public ClassFilter getClassFilter() {
- return this;
- }
-
- /**
- * 获取用于方法匹配的 MethodMatcher 实例。
- *
- * @return MethodMatcher 实例,用于匹配符合切点表达式的方法
- */
- @Override
- public MethodMatcher getMethodMatcher() {
- return this;
- }
- }
- public class HelloService {
- public String hello() {
- System.out.println("hello word! yihuiComeOn");
- return "hello word! yihuiComeOn";
- }
- }
- public class PointcutExpressionTest {
- @Test
- public void testPointcutExpression() throws Exception {
- AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut("execution(* service.HelloService.*(..))");
- Class
clazz = HelloService.class; - Method method = clazz.getDeclaredMethod("hello");
-
- System.out.println("切点表达式匹配结果-类匹配:"+pointcut.matches(clazz));
- System.out.println("切点表达式匹配结果-方法匹配:"+pointcut.matches(method, clazz));
- }
- }
- 切点表达式匹配结果-类匹配:true
- 切点表达式匹配结果-方法匹配:true