Spring AOP详解

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(修饰符 返回类型 包.类.方法(参数) 异常)
  • 修饰符:可以是publicprivate等,但在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。
Spring AOP(面向切面编程)是Spring框架中的一个模块,用于提供横切关注点(Cross-Cutting Concerns)的支持。横切关注点是与应用程序的核心业务逻辑无关的功能,例如日志记录、性能统计、事务管理等。 在Spring AOP中,通过定义切面(Aspect)来捕获横切关注点,并将其应用到目标对象的方法中。切面由切点(Pointcut)和通知(Advice)组成。切点定义了在何处应用通知,通知则定义了在切点处执行的操作。 Spring AOP支持以下几种类型的通知: 1. 前置通知(Before Advice):在目标方法执行之前执行的通知。 2. 后置通知(After Advice):在目标方法执行之后执行的通知,不管方法是否抛出异常。 3. 返回通知(After Returning Advice):在目标方法成功执行并返回结果后执行的通知。 4. 异常通知(After Throwing Advice):在目标方法抛出异常后执行的通知。 5. 环绕通知(Around Advice):围绕目标方法执行的通知,可以在方法调用前后执行自定义操作。 除了通知,Spring AOP还支持引入(Introduction)和切点表达式(Pointcut Expression)等功能。引入允许为目标对象添加新的接口和实现,而切点表达式则允许开发人员定义切点的匹配规则。 要在Spring应用程序中使用AOP,需要进行以下步骤: 1. 引入Spring AOP的依赖。 2. 配置AOP代理。 3. 定义切面和通知。 4. 配置切点和通知之间的关系。 总之,Spring AOP提供了一种便捷的方式来处理横切关注点,使得开发人员可以将关注点与核心业务逻辑分离,提高代码的可维护性和可重用性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值