Spring AOP
面向切面编程,Spring框架中的核心功能之一。
将程序中不同方法中非核心业务(日志记录、安全性检查、事务管理)、重复性的代码抽离出来,使得核心业务逻辑与这些额外的功能解耦合(实现低耦合)。
核心概念
- 切面(Aspect):切面是AOP的基本单位,它包含了横切关注点的定义,即要在程序的哪些地方插入额外的行为。(执行的什么操作)
- 连接点(Join Point):程序执行的某个点,比如方法的调用或异常的抛出。在Spring AOP中,连接点通常指的是方法执行。
- 切入点(Pointcut):用来定义切面将要插入到哪些连接点上。可以使用表达式来匹配方法或类。(在哪些核心业务方法上执行)
- 通知(Advice):通知是指切面在连接点执行的具体操作。通知可以在方法调用前、后,甚至在抛出异常时执行。(什么时候执行)
- 目标对象(Target Object):目标对象是指被AOP代理的对象,通知会在该对象的方法执行时被应用。
- 代理(Proxy):AOP通过动态代理创建代理对象,该代理对象会增强目标对象的功能。
- 织入(Weaving):将切面应用到目标对象并创建代理对象的过程叫做织入。Spring AOP使用运行时织入,通常是通过JDK动态代理或CGLIB字节码生成来实现。
代码示例
以下代码实现了以下两个功能
- 模拟一个1秒的延迟,并输出实际执行的延迟
- 模拟一个0.5秒的延迟,并输出实际执行的延迟
其中"输出并输出实际执行的延迟"是两个方法中重复的业务,将其放到切面中实现。
XML依赖配置
<dependencies>
<!-- Spring AOP -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
业务接口和实现
package com.example.service;
public interface MyService {
void performTask();
void performAnotherTask();
}
package com.example.service;
import org.springframework.stereotype.Service;
@Service
public class MyServiceImpl implements MyService {
@Override
public void performTask() {
System.out.println("Executing business logic in performTask...");
try {
Thread.sleep(1000); // 模拟方法执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void performAnotherTask() {
System.out.println("Executing business logic in performAnotherTask...");
try {
Thread.sleep(500); // 模拟方法执行时间
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
日志切面
package com.example.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 切入点:拦截MyService接口中所有方法
@Around("execution(* com.example.service.MyService.*(..))")
public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
// 执行目标方法
Object proceed = joinPoint.proceed();
long executionTime = System.currentTimeMillis() - start;
System.out.println(joinPoint.getSignature() + " executed in " + executionTime + "ms");
return proceed;
}
}
Spring Boot启动类
package com.example;
import com.example.service.MyService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application implements CommandLineRunner {
@Autowired
private MyService myService;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
@Override
public void run(String... args) throws Exception {
myService.performTask(); // 调用第一个方法
myService.performAnotherTask(); // 调用第二个方法
}
}
输出示例
Executing business logic in performTask...
void com.example.service.MyService.performTask() executed in 1001ms
Executing business logic in performAnotherTask...
void com.example.service.MyService.performAnotherTask() executed in 502ms
通知类型
通知是切面的核心内容,定义了在连接点处执行的行为(即在方法执行的什么时候触发切面代码)。Spring AOP中提供了以下几种通知类型:
前置通知(Before)
在目标方法执行之前运行的通知。
@Before("execution(* com.example.service.*.*(..))")
public void beforeAdvice(JoinPoint joinPoint) {
System.out.println("Before method: " + joinPoint.getSignature());
}
后置通知(After)
在目标方法执行之后运行,无论方法是否成功执行。
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
System.out.println("Method returned: " + result);
}
返回通知(After Returning)
在目标方法成功返回后执行。
@AfterReturning(pointcut = "execution(* com.example.service.*.*(..))", returning = "result")
public void afterReturningAdvice(JoinPoint joinPoint, Object result) {
System.out.println("Method returned: " + result);
}
异常通知(After Throwing)
在目标方法抛出异常后执行。
@AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "error")
public void afterThrowingAdvice(JoinPoint joinPoint, Throwable error) {
System.out.println("Method threw exception: " + error);
}
环绕通知(Around)
包裹目标方法的执行。可以在方法调用前后自定义逻辑,并且可以完全控制方法是否执行。
@Around("execution(* com.example.service.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Before method: " + joinPoint.getSignature());
Object result = joinPoint.proceed(); // 执行目标方法
System.out.println("After method: " + joinPoint.getSignature());
return result;
}
Pointcut表达式
切入点定义了哪些连接点应该应用通知(在哪些方法中使用)。Spring使用AspectJ的表达式语言来定义Pointcut表达式。
基本格式
execution(修饰符 返回类型 包.类.方法(参数) 异常)
- 修饰符:可以是
public
、private
等,但在Pointcut中一般省略。 - 返回类型:
*
表示匹配任何返回类型。 - 包名:可以精确匹配某个包或使用
..
匹配多个包。 - 类名:可以指定具体类,也可以用
*
通配符匹配任意类。 - 方法名:可以指定具体方法,也可以用
*
通配符匹配任意方法。 - 参数:
..
表示匹配任意参数。 - 异常:可以忽略不写。
具体示例
匹配某个类中的所有方法
@Around("execution(* com.example.service.MyService.*(..))")
匹配某个包中的所有类的所有方法
@Around("execution(* com.example.service..*.*(..))")
匹配某个包中的所有public
方法
@Around("execution(public * com.example.service..*.*(..))")
匹配特定方法名
@Around("execution(* com.example.service.MyService.performTask(..))")
匹配返回值为void
的方法
@Around("execution(void com.example.service.MyService.*(..))")
匹配带有特定参数的方法
@Around("execution(* com.example.service.MyService.*(String, ..))")
匹配所有带有特定注解的方法
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
组合多个切入点
@Around("execution(* com.example.service.MyService.*(..)) || execution(* com.example.repository.MyRepository.*(..))")
匹配某个包及其子包中的类,且类名以Service
结尾
@Around("execution(* com.example..*Service.*(..))")
匹配特定类的构造方法
@Around("execution(com.example.service.MyService.new(..))")
涉及到的注解说明
@Service
Spring Framework中的注解,注在类上。
标识了这个类是一个业务逻辑层的服务 Bean。这意味着当 Spring 应用启动时,该 Bean 会被自动创建并加入到 Spring 应用上下文中。
@Override
java中的注解,注在方法上。
用于表示被标注的方法是一个重写方法。
@Aspect(Spring AOP相关)
Spring AOP中的注解,注在类上。
一般@Aspect、@Component同时使用,用于定义切面类。
@Component
Spring中的注解,注在类上。
注在类上,声明一个类为配置类,用于取代bean.xml配置文件注册bean对象。
@Around(Spring AOP相关)
Spring AOP中的注解,注在方法上。
用于定义环绕通知,包裹了目标方法的整个执行过程,允许你在方法执行之前和之后都执行一些逻辑。
环绕通知的方法需要一个参数类型为 ProceedingJoinPoint
,这是AOP提供的一个对象,代表当前被切入的连接点。你可以通过调用 proceed()
方法来执行目标方法。
@SpringBootApplication
Spring Boot中的注解,注在类上。
是由@SpringBootConfiguration(同@Configuration)、@EnableAutoConfiguration(开启自动配置)、@ComponentScan(包扫描注解,根据定义的扫描路径,把符合扫描规则的类装配到spring容器中)三个注解组合而成的,用于标记说明这个类是springboot的主配置类
@Autowired
Spring中的注解。
将存在依赖关系自动注入到类中,可以用于自动装配一个类的成员变量、构造函数或者方法,以实现依赖注入。
默认按照类型装配Bean。
注意:
- 在进行依赖注入时,需要确保注入目标的类型和上下文中的 Bean 类型是兼容的,否则可能会导致注入失败。
- 如果有多个候选 Bean,可以使用 @Qualifier 注解来指定具体的 Bean。
- 可以使用 @Autowired 的 required 属性来控制是否强制进行依赖注入,默认值为 true。