跳到主要内容

spring实现不同通知

阅读需 4 分钟
wqz

介绍

这个项目是一个基于 Spring 框架 的面向切面编程(AOP)案例,展示了如何通过 AOP 在方法执行的不同阶段(前置、后置、返回值)插入日志记录功能。项目通过实现一个简单的服务类 UserinfoService,并利用切面类 LoggingAspect 来分别在方法调用前后记录日志信息。

classDiagram
MainApp --> AppConfig : Uses
MainApp --> UserinfoService : Calls
UserinfoService --> LoggingAspect : AOP advice
AppConfig --> LoggingAspect : Configures
AppConfig --> UserinfoService : Configures

解释

MainApp 依赖于 AppConfig 配置类,获取 UserinfoService Bean 并调用方法。

UserinfoService 受到 LoggingAspect 的 AOP 通知(切面)。

AppConfig 配置类配置了 UserinfoServiceLoggingAspect

操作

1. 创建项目结构

创建 com.mtw.test 包,并在该包中添加Service.UserinfoService 服务类和 Aspect.LoggingAspect切面类。

同样我们需要一个配置类来告诉 Spring 我们的应用有哪些包需要被扫描、哪些功能需要启用,所以创建一个Config.AppConfig配置类,以及一个Test.MainApp测试类来运行 UserinfoService 中的 foo1foo2 方法,并观察 LoggingAspect 的切面通知是否会执行。

文件和文件夹结构示例
src
└── main
└── java
└── com
└── mtw
└── test
├── aspect
│ └── LoggingAspect.java
├── service
│ └── UserinfoService.java
├── config
│ └── AppConfig.java
└── Test
└── MainApp.java
image-20241109211816260

2. 定义 UserinfoService

这是一个业务逻辑类,负责执行核心功能。在这里,我们定义了两个简单的业务方法:

  • foo1():打印"foo1被调用"
  • foo2():打印"foo2被调用"
classDiagram
class UserinfoService {
+void foo1()
+void foo2()
}

package com.mtw.test.Service;

import org.springframework.stereotype.Service;

@Service
public class UserinfoService {

public void foo1(){
System.out.println("foo1被调用");
}

public void foo2(){
System.out.println("foo2被调用");
}

}

3. 定义切面类 LoggingAspect

这是一个切面类,用于实现 AOP(面向切面编程)。切面中定义了多个通知方法,这些方法会在 foo1()foo2() 执行时被调用,记录不同阶段的日志。

  • 前置通知:方法执行之前触发,记录 "前置通知"。
  • 后置通知:方法执行之后触发,记录 "后置通知"。
  • 返回值通知:方法执行完并返回值之后触发,记录 "返回值通知"。

[!WARNING]

要使用 @Aspect 注解和 Spring AOP 功能,确保在项目的 pom.xml 文件中添加以下 Spring AOP 依赖:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.10</version> <!-- 使用适合你的Spring版本 -->
</dependency>

此外,还需要以下依赖,以确保 Spring AOP 能正常工作:

<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.10</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.10</version>
</dependency>

这些依赖项允许项目使用 @Aspect 注解并支持面向切面的编程功能。

image-20241109204547684

classDiagram
class LoggingAspect {
+void beforeFoo1()
+void afterFoo1()
+void afterReturningFoo1()
+void beforeFoo2()
+void afterFoo2()
+void afterReturningFoo2()
}

package com.mtw.test.Aspect;

import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.AfterReturning;
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.mtw.test.Service.*.*(..))")
public void beforeAdvice() {
System.out.println("前置通知");
}

@After("execution(* com.mtw.test.Service.*.*(..))")
public void afterAdvice() {
System.out.println("后置通知");
}

@AfterReturning("execution(* com.mtw.test.Service.*.*(..))")
public void afterReturningAdvice() {
System.out.println("返回值通知");
}

}

4. 配置 Spring AOP

这个类是 Spring 配置类,配置了 Spring AOP 和组件扫描。通过 @EnableAspectJAutoProxy 启用 AOP 功能,并通过 @ComponentScan 告诉 Spring 扫描 com.mtw.test 包下的所有组件(如 UserinfoServiceLoggingAspect)。

classDiagram
class AppConfig {
+@Configuration
+@ComponentScan(basePackages="com.mtw.test")
+@EnableAspectJAutoProxy
}

package com.mtw.test.Config;

import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(basePackages = "com.mtw.test")
@EnableAspectJAutoProxy
public class Appconfig {

}

5. 测试代码

这是一个简单的测试类,负责启动 Spring 容器并调用 UserinfoService 中的方法(foo1()foo2())。通过运行这个类,你可以验证切面通知是否在方法执行的各个阶段被正确触发。

classDiagram
class MainApp {
+void main(String[] args)
+ApplicationContext context
+UserinfoService userinfoService
}

package com.mtw.test.Test;

import com.mtw.test.Config.Appconfig;
import com.mtw.test.Service.UserinfoService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

public class MainApp {
public static void main(String[] args) {

ApplicationContext context = new AnnotationConfigApplicationContext(Appconfig.class);
UserinfoService userinfoService = context.getBean(UserinfoService.class);

userinfoService.foo1();
System.out.println("----------");
userinfoService.foo2();
}

}

6. 运行和输出

运行 MainApp,控制台输出应如下:

运行结果
前置通知
foo1被调用
返回值通知
后置通知
----------
前置通知
foo2被调用
返回值通知
后置通知
image-20241109212509327

7. 解释

sequenceDiagram
participant C as Client
participant S as UserinfoService
participant L as LoggingAspect

C->>S: Call foo1()
L->>S: 前置通知 (Before Advice)
S->>S: 执行 foo1 方法
L->>S: 返回值通知 (After Returning Advice)
L->>S: 后置通知 (After Advice)
C->>S: Call foo2()
L->>S: 前置通知 (Before Advice)
S->>S: 执行 foo2 方法
L->>S: 返回值通知 (After Returning Advice)
L->>S: 后置通知 (After Advice)
  • 前置通知:在目标方法执行前触发
  • 后置通知:在目标方法执行后触发,无论是否抛出异常
  • 返回值通知:在目标方法正常执行并返回后触发
分享这篇文章
Loading Comments...