Spring 框架提供了强大的面向切面编程(AOP)支持,使得我们能够更方便的实现横切关注点的功能,比如日志记录、事务管理等。本文将深入探讨 Spring AOP 的概念、原理以及如何在应用程序中应用 AOP。
AOP 是一种编程范式,它允许将横切关注点与业务逻辑分离。横切关注点是那些跨足多个模块的关注点,比如日志、事务、方法执行时间等。通过 AOP 我们可以在不修改原始业务逻辑代码的情况下,将这些关注点模块化。
概念往往都是让人读不懂的,接下来让我们来举例说明一下:
首先看看我们的需求是什么呢?我们在实际开发过程中会写了无数个方法,想在每个方法开头和结束打印一下日志,例如某某方法开始了和某某方法结束了。
如下图这样:
是不是可以很清晰的发现一个问题,有无数个方法我们就需要在无数个方法里面分别加上这两个代码。。。
使用 AOP 面向切面编程,统一来一下。这就好像我们想把一些纸剪成两半似的,一张一张的剪就很慢(和上面现象一样),我们可以给它们摞起来然后用剪刀一刀下去,咔!全部剪开(AOP面向切面编程)。
就变成了下图这样:
是不是突然发现爽歪歪啦!
切面是一种模块化的单元,它包含了横切关注点的实现,在 Spring AOP 中,切面是由切点和通知组成的。
切点定义了在何处应用切面的规则,它是一个表达式,用于匹配连接点(在应用中执行的特定代码位置),切点决定了切面在哪里生效。
通知是切面的具体实现,它定义了在何时、何地、以及如何应用切面的逻辑。常见的通知类型包括前置通知、后置通知、异常通知、最终通知和环绕通知。
连接点是在应用中执行的特定代码位置,如方法调用、方法执行、异常抛出等,它是切点的实际执行实例。
织入是将切面应用到目标对象并创建代理对象的过程。织入可以在编译时、类加载时、运行时进行。
在 pom.xml
中添加 spring-aop
和 aspectjweaver
两个依赖(这里只写了本次新添加的两个)
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.20.1</version>
</dependency>
创建一个简单的业务类,例如 DemoController
,其中包含一个待切入的方法
package com.cheney.koala.controller;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class DemoController {
public void print(){
System.out.println("Hello");
}
}
创建一个切面类,例如 LoggingAspect
,用于定义在方法执行前后打印日志的通知
package com.cheney.koala.aspect;
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.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
@Pointcut("execution( * com.cheney.koala.controller.*.*(..) )")
public void demoActionExecution() {
}
@Before("demoActionExecution()")
public void logBefore(JoinPoint joinPoint) {
System.out.println("方法 " + joinPoint.getSignature().getName() + " 开始啦");
}
@After("demoActionExecution()")
public void logAfter(JoinPoint joinPoint) {
System.out.println("方法 " + joinPoint.getSignature().getName() + " 结束啦");
}
}
在这个切面中,我们使用 @Aspect
注解标识这是一个切面类。
@Pointcut("execution( * com.cheney.koala.controller.*.*(..) )")
这句代码是一个 Spring AOP 中的切点表达式,用于定义在哪些方法上应用切面。
@Pointcut
:这是一个注解,用于定义切点,即在哪些方法上应用切面execution
:这是切点表达式的一部分,表示匹配方法的执行*
:这是通配符,表示匹配任意返回类型的方法com.cheney.koala.controller
:这是包名,表示匹配该包下的所有类*.*(..)
:这也是通配符,表示匹配该包下所有类的所有方法因此,整个表达式的意思就是匹配 com.cheney.koala.controller
包下所有类的所有方法,无论返回类型和参数类型是什么,都是我想切的对象(也即前后加入方法开始和方法结束的日志)
在 @Before
和 @After
注解的通知方法中,使用 JoinPoint
获取连接点信息,包括方法签名等
在 KoalaApplication.java
中,添加了默认启动后调用 DemoController
里的 print
方法,仅仅是为了测试方便,没有别的特殊用意。
package com.cheney.koala;
import com.cheney.koala.controller.DemoController;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.ConfigurableApplicationContext;
@SpringBootApplication
public class KoalaApplication {
public static void main(String[] args) {
ConfigurableApplicationContext context = SpringApplication.run(KoalaApplication.class, args);
DemoController demo = context.getBean(DemoController.class);
demo.print();
}
}
运行应用程序,你将看到如下输出
方法 print 开始啦
Hello
方法 print 结束啦
这表明我们成功地通过 AOP 切面在方法执行前后分别打印了日志
**切面 **
使用 @Aspect
注解标识这是一个切面类。在类中使用 @Pointcut
注解定义了一个切点 demoActionExecution
,用于匹配我想切的对象
切点
execution( * com.cheney.koala.controller.*.*(..) )
表示 com.cheney.koala.controller
包下所有类的所有方法,无论返回类型和参数类型是什么,都是我想切的对象
连接点
使用 JoinPoint
获取连接点信息,包括方法签名等
通知
使用 @Before
和 @After
注解定义了两个通知方法,分别在方法执行前后执行
织入
在运行时,Spring AOP 将切面织入目标对象的方法中,从而在方法执行前后执行通知
通过 Spring AOP,我们能够将关注点(如日志记录)与业务逻辑分离,使得代码更加清晰、可维护。在实际应用中,AOP 可以应用于诸如事务管理、安全性、性能监控等方面,为应用程序提供更高的可扩展性和可维护性。本文通过一个日志打印的例子,详细介绍了在 Spring AOP 中切面、切点、通知、连接点和织入的概念,并成功地使用 AOP 实现了在方法执行前后分别打印日志的功能。这种方式帮助我们实现了横切关注点的代码重用,提高了代码的可维护性和可读性,也希望通过本文的介绍,可以将 AOP 应用到具体项目开发中。