likes
comments
collection
share

【SpringBoot】建议收藏 Spring AOP & Aspectj 框架 快速入门~

作者站长头像
站长
· 阅读数 2

Spring AOP定义

到底什么是AOP,很多小伙伴八股文背的6的一,但是实际业务可能工作两年都没用过。所以来说AOP到底是什么玩意?

AOP 是 Aspect Oriented Programming 的缩写,译为面向切向编程。

图片来理解:

假设我有四个行为:

【SpringBoot】建议收藏 Spring AOP & Aspectj 框架 快速入门~

现在有个需求:

  • 在我所有行为之前我都需要 洗手
  • 在我所有行为之后我都需要 上厕所

我们应该怎么办:

  • 解决办法一:每个方法之前加入 洗手 逻辑, 每个方法之后加入 上厕所 逻辑。
  • 解决办法二:抽取出一个公用的 洗手 和 上厕所 方法,然后每个行为之前调用 洗手方法,每个行为之后调用 上厕所方法。

如果我们不允许修改源码呢?

OK不多说了,AOP就可以解决这些问题。

【SpringBoot】建议收藏 Spring AOP & Aspectj 框架 快速入门~

所以AOP其实就是一种思想,一种抽象出 点、面、前置通知、后置通知、环绕通知 的一种思想。

emmmmm,你这不是在难为我嘛,你这是强行为了使用AOP而使用AOP,我不服,公共方法就是比AOP好,而且也很好维护啊!

其实公共方法当然也很好,但是AOP却能使用不同的场景,在我们现在的业务场景复杂多变,公共方法也不能完全适用于所有的场景,这个时候AOP就应运而生了。

Spring AOP 和 Aspectj框架

Spring 框架提供了两种 AOP 的实现方式:Spring AOP 和 AspectJ。这两种方式都可以用于在 Spring 应用中实现切面编程,但它们具有一些不同的特点和使用方式。

  1. Spring AOP:

    • 基于动态代理技术,通过 JDK 动态代理或 CGLIB 字节码增强来实现 AOP。
    • 只支持方法级别的切面,无法对类级别的切面进行处理。
    • 只能在 Spring 托管的 Bean 上应用切面,无法对非 Spring Bean 进行切面。
    • 只支持基于方法执行的切点表达式,如 execution()
    • 配置简单,无需额外的编译步骤,只需在 XML 配置文件或注解中定义切面和通知。
    • 适用于简单的 AOP 需求和对 Spring 生态系统的集成。
  2. AspectJ:

    • 是一种独立于 Spring 的 AOP 框架,提供更强大和灵活的 AOP 功能。
    • 通过字节码增强(AspectJ 编译器)或运行时动态代理(Spring AOP 支持)来实现 AOP。
    • 支持方法级别和类级别的切面,可以对任意的类和方法进行切面处理。
    • 可以在任何 Java 项目中使用,不限于 Spring 应用。
    • 支持多种切点表达式,如 execution()within()args() 等。
    • 配置较为复杂,需要额外的编译步骤和配置文件,如使用 AspectJ 编译器进行编译和生成 AspectJ 的配置文件。
    • 适用于复杂的 AOP 需求和对 AOP 功能的更高级控制。

综上所述,Spring AOP 是 Spring 核心框架自带的 AOP 实现,适用于简单的 AOP 需求和对 Spring 生态系统的集成;而 AspectJ 则是独立的 AOP 框架,提供更强大和灵活的 AOP 功能,适用于复杂的 AOP 需求和对 AOP 功能的更高级控制。JAVA项目中企业中多使用AspectJ。

SpringBoot 集成 Aspectj框架

1、Maven引入

版本使用的就是SpringBoot的版本

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2、快速入门

创建一个新的类,作为切面类。切面类是一个普通的Java类,用于定义切面的行为,使用@Aspect注解进行标记。

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    @Before("execution(* com.example.demo.controller.*.*(..))")
    public void beforeAdvice() {
        System.out.println("Before method execution");
    }
}

在Spring Boot的启动类中,使用@EnableAspectJAutoProxy注解来启用AspectJ自动代理。

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}

在控制器类中编写一些方法,以便在请求处理过程中触发切面逻辑。

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class DemoController {

    @GetMapping("/")
    public String hello() {
        return "Hello, World!";
    }
}

Aspectj 中常用 注解 解析

1、@Aspect

声明一个类是切面类。

import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

}

2、@Pointcut

@Pointcut是AspectJ框架中的注解,用于定义切入点。

切入点是在目标方法上执行通知的特定位置。

@Pointcut注解可以与@Before@After@Around等注解一起使用。

使用@Pointcut注解时,可以指定一个表达式来匹配目标方法。

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.example.demo.controller.*.*(..))")
    public void controllerMethods() {}

    @Before("controllerMethods()")
    public void beforeAdvice() {
        System.out.println("Before method execution");
    }
}

execution()

@Pointcut("execution(* com.example.demo.controller.*.*(..))")execution是用于匹配方法执行的切点指示符。它的语法如下:

execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)

各部分说明:

  • modifiers-pattern:可选项,用于指定方法的修饰符。例如,public表示公共方法,private表示私有方法,*表示任意修饰符。
  • ret-type-pattern:用于指定方法的返回类型。例如,void表示没有返回值,*表示任意返回类型。
  • declaring-type-pattern:可选项,用于指定方法所在的类或接口。可以使用通配符*来匹配任意类或接口。
  • name-pattern:用于指定方法的名称。可以使用通配符*来匹配任意方法名。
  • param-pattern:用于指定方法的参数类型。可以使用通配符*来匹配任意参数类型,或使用..表示任意个数或任意类型的参数。
  • throws-pattern:可选项,用于指定方法可能抛出的异常类型。可以使用通配符*来匹配任意异常类型。

例子:

  • execution(public * *(..)):匹配所有公共方法。
  • execution(* com.example.demo.controller.*.*(..)):匹配位于com.example.demo.controller包下的所有方法。
  • execution(* com.example.demo.controller.UserController.*(..)):匹配UserController类中的所有方法。
  • execution(* com.example.demo.controller.UserController.get*(..)):匹配以get开头的方法。
  • execution(* com.example.demo.controller.UserController.*(..) throws java.io.IOException):匹配抛出java.io.IOException异常的方法。

@annotation()

是一个切点函数,用于匹配被特定注解标记的方法。使用该切点函数来拦截带有指定注解的方法,并在其执行前后织入通知逻辑。

@Aspect
@Component
public class MyAspect {
   @Around("@annotation(com.example.demo.annotations.Loggable)")
   public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
       // 在目标方法执行前的逻辑
       System.out.println("Before method execution");
       
       // 执行目标方法
       Object result = joinPoint.proceed();
       
       // 在目标方法执行后的逻辑
       System.out.println("After method execution");
       
       return result;
   }
}

3、@Before

在目标方法执行之前执行通知。

@Aspect
@Component
public class MyAspect {
   @Before("execution(* com.example.demo.controller.*.*(..))")
   public void beforeMethod(JoinPoint joinPoint) {
       // 在目标方法执行之前执行的逻辑
       System.out.println("Before method execution");
   }
}

4、@After

在目标方法执行之后无论是否发生异常都执行通知。

@Aspect
@Component
public class MyAspect {
   @After("execution(* com.example.demo.service.*.*(..))")
   public void afterMethod(JoinPoint joinPoint) {
       // 在目标方法执行之后执行的逻辑
       System.out.println("After method execution");
   }
}

5、@AfterReturning

在目标方法返回结果后执行通知。

@Aspect
@Component
public class MyAspect {
   @AfterReturning(pointcut = "execution(* com.example.demo.service.*.*(..))", returning = "result")
   public void afterReturningMethod(JoinPoint joinPoint, Object result) {
       // 在目标方法成功返回后执行的逻辑
       System.out.println("After method returning");
       System.out.println("Result: " + result);
   }
}

afterReturningMethod方法中,可以通过JoinPoint参数获取目标方法的详细信息,通过returning参数获取目标方法的返回结果。

6、@AfterThrowing

在目标方法抛出异常后执行通知。

@Aspect
@Component
public class MyAspect {
   @AfterThrowing(pointcut = "execution(* com.example.demo.service.*.*(..))", throwing = "exception")
   public void afterThrowingMethod(JoinPoint joinPoint, Exception exception) {
       // 在目标方法抛出异常后执行的逻辑
       System.out.println("After method throwing");
       System.out.println("Exception: " + exception.getMessage());
   }
}

afterThrowingMethod方法中,可以通过JoinPoint参数获取目标方法的详细信息,通过throwing参数获取目标方法抛出的异常。

7、@Around

在目标方法前后执行通知,可以控制目标方法的执行。

@Aspect
@Component
public class MyAspect {
   @Around("execution(* com.example.demo.service.*.*(..))")
   public Object aroundMethod(ProceedingJoinPoint joinPoint) throws Throwable {
       // 在目标方法执行前的逻辑
       System.out.println("Before method execution");
       
       // 执行目标方法
       Object result = joinPoint.proceed();
       
       // 在目标方法执行后的逻辑
       System.out.println("After method execution");
       
       return result;
   }
}

aroundMethod方法中,通过ProceedingJoinPoint参数调用proceed()方法来执行目标方法。在proceed()方法之前的逻辑表示在目标方法执行前执行,proceed()方法之后的逻辑表示在目标方法执行后执行。

觉得作者写的不错的,值得你们借鉴的话,就请点一个免费的赞吧!这个对我来说真的很重要。

转载自:https://juejin.cn/post/7298635800631541812
评论
请登录