Java面试篇-AOP专题(什么是AOP、AOP的几个核心概念、AOP的常用场景、使用AOP记录操作日志、Spring中的事务是如何实现的)

embedded/2024/9/22 16:53:12/

文章目录

  • 1. 什么是AOP
  • 2. AOP的几个核心概念
  • 3. AOP的常用场景
  • 4. 使用AOP记录操作日志
    • 4.1 准备工作
      • 4.1.1 引入Maven依赖
      • 4.1.2 UserController.java
      • 4.1.3 User.java
      • 4.1.4 UserService.java
    • 4.2 具体实现(以根据id查询用户信息为例)
      • 4.2.1 定义切面类(切入点和环绕增强)
      • 4.2.2 自定义注解
      • 4.2.3 为方法添加自定义注解
    • 4.3 测试
  • 5. Spring中的事务是如何实现的

1. 什么是AOP

AOP(Aspect Oriented Programming),面向切面编程

AOP 主要的功能是将 与业务无关,但却对多个对象产生影响的公共行为或逻辑 抽取并封装为一个可重用的模块,这个可重用的模块称为切面(Aspect)

AOP 能够减少系统中的重复代码,降低模块间的耦合度,同时提高系统的可维护性


如果想了解 Spring 事务失效的情况,可以参考我的另一篇博文:Spring中事务失效的常见场景及解决方法

2. AOP的几个核心概念

AOP 的核心概念主要包括以下几个:

  1. 切面(Aspect)
    • 切面是AOP中的一个核心概念,它代表了一个横切关注点(cross-cutting concern),即将多个模块中共有的行为抽象出来形成的一个独立模块。在Spring AOP中,切面通常是通过使用@Aspect注解的类来实现的
  2. 连接点(Join Point)
    • 连接点是在程序执行过程中的一个特定点,例如方法的调用、异常的抛出等。在Spring AOP中,只支持方法的连接点
  3. 切入点(Pointcut)
    • 切入点是一组连接点的定义,它定义了哪些连接点会被切面所拦截。通常使用正则表达式或者特定的表达式语言来指定哪些方法会被拦截
  4. 通知(Advice)
    • 通知定义了切面在特定的连接点上要执行的动作。通知有多种类型:
      • 前置通知(Before):在连接点之前执行
      • 后置通知(After):在连接点之后执行,无论方法是否正常结束
      • 返回通知(After Returning):在连接点正常返回后执行
      • 异常通知(After Throwing):在连接点抛出异常后执行
      • 环绕通知(Around):包围一个连接点的通知,可以在方法调用前后执行自定义的行为
  5. 目标对象(Target Object)
    • 目标对象是指被一个或多个切面所通知的对象。在Spring AOP中,目标对象通常是Spring容器中的Bean
  6. 代理(Proxy)
    • AOP通过代理模式来实现对目标对象的增强。代理对象会在运行时创建,并用来代替目标对象。当调用代理对象的方法时,代理会根据切面的配置来执行相应的通知
  7. 织入(Weaving)
    • 织入是将切面的通知应用到目标对象并创建新的代理对象的过程。这个过程可以在编译时、类加载时或运行时进行

3. AOP的常用场景

  1. 记录操作日志:记录日志属于公共的行为,每一个 Service 都需要记录操作日志,直接在每一个 Service 里面编写记录日志的代码不太合适,使用 AOP 就可以很方便地完成记录日志操作
  2. 处理缓存:如果某些业务要添加缓存,直接写在 Service 层会造成代码耦合的情况,我们可以利用 AOP 的切面,拦截需要添加缓存的业务方法,为业务方法添加缓存
  3. Spring 中内置的事务处理

4. 使用AOP记录操作日志

4.1 准备工作

4.1.1 引入Maven依赖

在 SpringBoot 项目的 pom.xml 文件中引入 aspectjweaver 的 Maven 依赖

<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId>
</dependency>

java_62">4.1.2 UserController.java

在这里插入图片描述

java">import cn.edu.scau.aop.pojo.User;
import cn.edu.scau.aop.service.UserService;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/user")
public class UserController {private final UserService userService;public UserController(UserService userService) {this.userService = userService;}@GetMapping("/getById/{id}")public User getById(@PathVariable("id") Integer id) {return userService.getById(id);}@PostMapping("/save")public void save(@RequestBody User user) {userService.save(user);}@PutMapping("/update")public void update(User user) {userService.update(user);}@DeleteMapping("/delete/{id}")public void delete(@PathVariable("id") Integer id) {userService.delete(id);}}

java_105">4.1.3 User.java

java">public class User {private Integer id;private String name;private Integer age;private Short sex;private Short status;private String image;public Integer getId() {return id;}public void setId(Integer id) {this.id = id;}public String getName() {return name;}public void setName(String name) {this.name = name;}public Integer getAge() {return age;}public void setAge(Integer age) {this.age = age;}public Short getSex() {return sex;}public void setSex(Short sex) {this.sex = sex;}public Short getStatus() {return status;}public void setStatus(Short status) {this.status = status;}public String getImage() {return image;}public void setImage(String image) {this.image = image;}@Overridepublic String toString() {return "User{" +"id=" + id +", name='" + name + '\'' +", age=" + age +", sex=" + sex +", status=" + status +", image='" + image + '\'' +'}';}}

java_185">4.1.4 UserService.java

java">import cn.edu.scau.aop.pojo.User;
import org.springframework.stereotype.Service;@Service
public class UserService {public User getById(Integer id) {return new User();}public void save(User user) {System.out.println("保存用户信息");}public void update(User user) {System.out.println("更新用户信息");}public void delete(Integer id) {System.out.println("删除用户信息");}}

4.2 具体实现(以根据id查询用户信息为例)

在我们的开发过程中,大多都有记录操作日志的需求

在这里插入图片描述

当用户访问某一个接口时,我们需要记录发起请求的用户是谁,请求的方式是什么,访问地址是什么,访问了哪一个模块,登录的 IP 地址,操作时间等


接下来我们分析一下利用 AOP 记录操作日志的具体实现方式

假如后台有四个请求的接口:登录、新增用户、更新用户、删除用户

在这里插入图片描述

我们以查询用户为例,利用 AOP 提供的环绕通知做一个切面

在这里插入图片描述

4.2.1 定义切面类(切入点和环绕增强)

用 @Aspect 注解表名当前类是一个切面类,而且切面类需要交由 Spring 管理

java">import cn.edu.scau.aop.annotation.Log;
import jakarta.servlet.http.HttpServletRequest;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import java.lang.reflect.Method;
import java.util.Date;/*** 切面类*/
@Component
@Aspect
public class SystemAspect {@Pointcut("@annotation(cn.edu.scau.aop.annotation.Log)")private void pointcut() {}@Around("pointcut()")public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 通过解析 session 或 token 获取用户名// 获取被增强类和方法的信息Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;// 获取被增强的方法对象Method method = methodSignature.getMethod();// 从方法中解析注解if (method != null) {Log logAnnotation = method.getAnnotation(Log.class);System.out.println(logAnnotation.name());}// 方法名字String name = null;if (method != null) {name = method.getName();}System.out.println("方法名:" + name);// 通过工具类获取Request对象RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;HttpServletRequest request = null;if (servletRequestAttributes != null) {request = servletRequestAttributes.getRequest();}// 访问的URLString url = null;if (request != null) {url = request.getRequestURI();}System.out.println("访问的URL:" + url);// 请求方式String methodName = null;if (request != null) {methodName = request.getMethod();}System.out.println("请求方式:" + methodName);// 登录IPString ipAddress = null;if (request != null) {ipAddress = getIpAddress(request);}System.out.println("登录IP:" + ipAddress);// 操作时间System.out.println("操作时间:" + new Date());// 将操作日志保存到数据库return joinPoint.proceed();}/*** 获取 IP 地址** @param request HttpServletRequest* @return String*/public String getIpAddress(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;}}

切面类中有一个切点表达式

在这里插入图片描述

这个切点表达式找的是一个注解,也就是说,如果某个方法上添加了 Log 注解,进化就会进入到环绕通知中进行增强


环绕通知增强如下

在这里插入图片描述

java">@Around("pointcut()")
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {// 通过解析 session 或 token 获取用户名// 获取被增强类和方法的信息Signature signature = joinPoint.getSignature();MethodSignature methodSignature = (MethodSignature) signature;// 获取被增强的方法对象Method method = methodSignature.getMethod();// 从方法中解析注解if (method != null) {Log logAnnotation = method.getAnnotation(Log.class);System.out.println("模块名称:" + logAnnotation.name());}// 方法名字String name = null;if (method != null) {name = method.getName();}System.out.println("方法名:" + name);// 通过工具类获取Request对象RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes;HttpServletRequest request = null;if (servletRequestAttributes != null) {request = servletRequestAttributes.getRequest();}// 访问的URLString url = null;if (request != null) {url = request.getRequestURI();}System.out.println("访问的URL:" + url);// 请求方式String methodName = null;if (request != null) {methodName = request.getMethod();}System.out.println("请求方式:" + methodName);// 登录IPString ipAddress = null;if (request != null) {ipAddress = getIpAddress(request);}System.out.println("登录IP:" + ipAddress);// 操作时间System.out.println("操作时间:" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));// 将操作日志保存到数据库return joinPoint.proceed();
}/*** 获取 IP 地址** @param request HttpServletRequest* @return String*/
public String getIpAddress(HttpServletRequest request) {String ip = request.getHeader("x-forwarded-for");if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getHeader("WL-Proxy-Client-IP");}if (ip == null || ip.isEmpty() || "unknown".equalsIgnoreCase(ip)) {ip = request.getRemoteAddr();}return "0:0:0:0:0:0:0:1".equals(ip) ? "127.0.0.1" : ip;
}

4.2.2 自定义注解

那 Log 注解是从哪里来的呢,其实是我们自定义的,注解中的 name 属性是模块的名称

在这里插入图片描述

java">import java.lang.annotation.*;@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Log {/*** 模块名称*/String name() default "";}

4.2.3 为方法添加自定义注解

我们在需要记录操作日志的方法上添加自定义注解

在这里插入图片描述

4.3 测试

启动项目后,我们在浏览器输入以下网址访问接口

http://localhost:8080/user/getById/1

查看控制台,发现操作日志已成功打印操作日志

在这里插入图片描述

5. Spring中的事务是如何实现的

Spring支持编程式事务管理和声明式事务管理两种方式

  • 编程式事务控制:需使用 TransactionTemplate 来进行实现,对业务代码有侵入性,项目中很少使用
  • 声明式事务管理:声明式事务管理建立在 AOP 之上,本质是通过 AOP 对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务

声明式事务的示意图

在这里插入图片描述

joinPoint.proceed 是真正要执行的目标对象的方法,在方法执行前开启事务,方法成功执行之后提交事务

如果方法在执行的过程中出错了,需要回滚事务,在 catch 代码块中会有一个回滚事务的操作


http://www.ppmy.cn/embedded/115135.html

相关文章

[SDX35+WCN6856]SDX35 + WCN6856 使用QCMAP启动WiFi操作方法

SDX35 SDX35介绍 SDX35设备是一种多模调制解调器芯片,支持 4G/5G sub-6 技术。它是一个4nm芯片专为实现卓越的性能和能效而设计。它包括一个 1.9 GHz Cortex-A7 应用处理器。 SDX35主要特性 ■ 3GPP Rel. 17 with 5G Reduced Capability (RedCap) support. Backward compati…

社交电商中“信任”基础与“链动 2+1 模式 O2O 商城小程序”的价值探索

摘要&#xff1a;本文深入探讨了在基于社交的商业模式中&#xff0c;“信任”作为重要基础条件的关键作用。详细分析了在产品同质化日益严重的当下&#xff0c;人与人之间口口相传的宣传方式优势。同时&#xff0c;全面引入“链动 21 模式 O2O 商城小程序”&#xff0c;深入阐述…

智谱AI:CogVideoX-2b——视频生成模型的得力工具

智谱AI&#xff1a;CogVideoX-2b——视频生成模型的得力工具 文章目录 CogVideoX 简介——它是什么&#xff1f;CogVideoX 具体部署与实践指南一、创建丹摩实例二、配置环境和依赖三、上传模型与配置文件四、开始运行五、Web UI 演示 CogVideoX 简介——它是什么&#xff1f; …

RK3568笔记六十一:MIPI摄像头应用程序编写

若该文为原创文章,转载请注明原文出处。 使用V4L2方式采集摄像头。 使用正点原子的例程测试没通过,后面使用一口君的测试采集到了摄像头 记录过程。 详细参考Camera | 4.瑞芯微平台MIPI摄像头应用程序编写-CSDN博客 一、介绍 V4L2设备驱动框架向应用层提供了一套统一、标…

使用 LocalStorage 提升前端体验

使用 LocalStorage 提升前端体验 在这个项目&#xff08;www.studytool.site&#xff09;中&#xff0c;本地缓存 (localStorage) 作为一种持久化存储机制&#xff0c;被广泛应用于用户设置和数据保存&#xff0c;确保用户在刷新页面或关闭浏览器后&#xff0c;仍然能够保留自…

CefSharp_Vue交互(Element UI)_WinFormWeb应用(3)---通过页面锁屏和关机(含示例代码)

一、预览 实现功能:通过vue标题栏按钮锁屏和关机 1.1 预览 1.2 代码 锁屏代码csharp LockWorkStation() 关机代码chsharp 注意vue代码参数和此参数一致(0/1/2) 方法ExitWindowsEx()

flex,promise里catch,作用域和作用域链和闭包,递归展开嵌套数组

目录 1. flex里shrink&#xff0c;grow和basis具体应用 2 promise里的catch 3 作用域和作用域链 4 递归展开嵌套数组 1. flex里shrink&#xff0c;grow和basis具体应用 <style> .container { display:flex; width:600px; }.div1 { flex:1 2 500px; }.div2 { flex:2 …

PHP项目中Vendor错误导致项目失败的解决方案

在PHP项目中&#xff0c;vendor目录通常用于存放通过Composer安装的依赖库。虽然这些依赖极大地提高了开发效率&#xff0c;但它们也可能成为项目失败的隐患。本文将探讨常见的Vendor错误及其解决方案。 #### 1. 常见的Vendor错误 ##### 1.1 版本不兼容 不同的依赖可能对PHP…