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

文章目录

  • 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>

4.1.2 UserController.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);}}

4.1.3 User.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 + '\'' +'}';}}

4.1.4 UserService.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 管理

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 注解,进化就会进入到环绕通知中进行增强


环绕通知增强如下

在这里插入图片描述

@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 属性是模块的名称

在这里插入图片描述

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.xdnf.cn/news/146539.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

基于uni-app的计算机类面试宝设计与实现(毕业论文)

目 录 1 前言 1 1.1 研究目的与意义 1 1.2 研究现状 1 1.3 论文结构 2 2 可行性分析 3 2.1 经济可行性 3 2.2 法律可行性 3 2.3 技术可行性 4 2.4 市场可行性 4 2.5 可行性分析结论 4 3 系统需求分析 4 3.1 用户需求分析 4 3.2 系统功能分析 5 3.3 系统性能需求分析 6 4 概要设…

【网络安全 | 靶机搭建】修改镜像源、更新软件源、安装git、更改python版本等

文章目录 0x00、必要准备0x01、修改镜像源0x02、更新软件源并清除缓存0x03、安装git0x04、更改默认Python版本为python30x05、安装增强功能0x06、vmware虚拟机导出iso0x00、必要准备 安装虚拟机时必须保存用户名、密码,用于后续操作,可以截图保存: 以下内容按个人需要进行配…

重生奇迹MU 强化玩法套路多 极品装备由你打造

欢迎来到重生奇迹MU的强化玩法指南&#xff01;想要打造极品装备吗&#xff1f;不可错过这篇文章&#xff0c;我们将为您揭开最多套路的强化技巧和窍门&#xff0c;帮您节省时间和资源&#xff0c;并带来最高效的升级结果。无论您是新手还是老玩家&#xff0c;本文适合所有级别…

AI浪潮新崛起:借助AI+实景/视频直播创新魅力,开启无人自动直播新时代!

AI浪潮新崛起&#xff1a;借助AI实景/视频直播创新魅力&#xff0c;开启无人自动直播新时代&#xff01; 在科技日新月异的今天&#xff0c;人工智能&#xff08;AI&#xff09;已不再仅仅是科幻电影中的桥段&#xff0c;它正以不可阻挡之势渗透到我们生活的方方面面&#xff…

工业物联网的海量数据如何呈现,可视化设计来助力

工业物联网产生的海量数据需要通过可视化设计来呈现&#xff0c;以帮助用户更好地理解和分析数据。 数据汇总和聚合&#xff1a; 对于大量的数据&#xff0c;可以通过汇总和聚合的方式来减少数据的数量&#xff0c;同时保留关键的信息。例如&#xff0c;将时间序列数据按照小…

Excel 冻结多行多列

背景 版本&#xff1a;office 2021 专业版 无法像下图内某些版本一样&#xff0c;识别选中框选的多行多列。 如下选中后毫无反应&#xff0c;点击【视图】->【冻结窗口】->【冻结窗格】后自动设置为冻结第一列。 操作 如下&#xff0c;要把前两排冻结起来。 选择 C1&a…

2024华为杯研赛D题保姆级教程思路分析+教程

2024年中国研究生数学建模竞赛D题保姆级教程思路分析 D题&#xff1a;大数据驱动的地理综合问题&#xff08;数学分析&#xff0c;统计学&#xff09; 关键词&#xff1a;地理、气候、统计&#xff08;细致到此题&#xff1a;统计指标、统计模型、统计结果解释&#xff09; …

视频压缩篇:适用于 Windows 的 10 款最佳视频压缩器

视频压缩器现在对许多想要减小视频大小的视频编辑者来说非常有名。但是&#xff0c;并非所有可以在网上找到的视频压缩器都能产生最佳输出。因此&#xff0c;我们搜索了可以无损压缩视频的最出色的视频压缩器应用程序。本文列出了您可以在离线、在线和手机上使用的十大最佳视频…

FastAdmin列表用echats渲染,使用表格的templateView实现一个图表渲染的功能

前言 FastAdmin中Bootstrap-table表格参数templateView拥有强大的自定义功能&#xff0c;这里我们使用templateView来实现一个图表渲染的功能。 首先我们itemtpl模板中的数据需要填充为一个JSON数据&#xff0c;包含column和data两列 ,chartdata为我们服务器返回的行中的数据。…

力扣-1035不相交的线(Java详细题解)

题目链接&#xff1a;力扣-1035不相交的线 前情提要&#xff1a; 因为本人最近都来刷dp类的题目所以该题就默认用dp方法来做。 dp五部曲。 1.确定dp数组和i下标的含义。 2.确定递推公式。 3.dp初始化。 4.确定dp的遍历顺序。 5.如果没有ac打印dp数组 利于debug。 每一…

nginx模块篇(四)

文章目录 四、Nginx的扩展模块4.1. Lua4.1.1 概念4.1.2 特性4.1.3 应用场景4.1.4 Lua的安装4.1.5 Lua的语法4.1.5.1 第一个Lua程序4.1.5.2 Lua的注释4.1.5.3 标识符4.1.5.4 关键字4.1.5.5 运算符4.1.5.6 全局变量&局部变量4.1.5.7 Lua数据类型nilbooleannumberstringtablef…

串口助手的qt实现思路

要求实现如下功能&#xff1a; 获取串口号&#xff1a; foreach (const QSerialPortInfo &serialPortInfo, QSerialPortInfo::availablePorts()) {qDebug() << "Port: " << serialPortInfo.portName(); // e.g. "COM1"qDebug() <<…

绿色数据中心:实现可持续发展和具备盈利能力的全闪存解决方案

数据中心成为了当今数字世界的支柱&#xff0c;负责存储、处理和分发驱动几乎所有数字服务产生&#xff08;从网上银行到即时消息&#xff09;的数据。这使得数字中心逐渐成为了现代商业基础设施的关键组成部分。 但是&#xff0c;随之而来的是&#xff0c;数据中心也已经成为…

基于asp.net固定资产管理系统设计与实现

博主介绍&#xff1a;专注于Java vue .net php phython 小程序 等诸多技术领域和毕业项目实战、企业信息化系统建设&#xff0c;从业十五余年开发设计教学工作 ☆☆☆ 精彩专栏推荐订阅☆☆☆☆☆不然下次找不到哟 我的博客空间发布了1000毕设题目 方便大家学习使用 感兴趣的…

如何着手创建企业数据目录?(三)权限管理及版本控制

前文导读&#xff1a; 《如何着手创建企业数据目录&#xff1f;&#xff08;一&#xff09;数据目录的设定》 《如何着手创建企业数据目录&#xff1f;&#xff08;二&#xff09;数据的命名与维护》 前面聊过了数据目录的设定、数据命名规则和维护规则&#xff0c;今天我们继续…

34.打字机效果 水平滚动贴合

打字机效果 创建打字机效果动画。 定义两个动画,typing 用于字符动画,blink 用于光标动画。使用 ::after 伪元素在容器元素中添加光标。使用 JavaScript 为内部元素设置文本,并设置包含字符数的 --characters 变量。这个变量用于文本动画。使用 white-space: nowrap 和 overflo…

【华为杯】2024数学建模研赛题目

2024数学建模研赛题目已经发布 各个赛题题目如下&#xff1a; A题 B题 C题 D题 E题 F题 赛题完整版在文末&#xff0c;点击下方名片。

离散型制造业MES系统主要功能介绍

一、离散型制造业的特点 离散型制造业是指生产过程中涉及多个独立工序或步骤&#xff0c;且这些工序之间相对独立、缺乏连续性的企业。其特点主要包括&#xff1a; 产品种类多&#xff0c;开发频繁&#xff1a; 离散型制造业通常需要进行多品种产品开发&#xff0c;产品种类繁…

OpenCV特征检测(2)边缘检测函数Canny()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 使用 Canny 算法 48在图像中查找边缘。 该函数使用 Canny 算法在输入图像中查找边缘&#xff0c;并在输出地图 edges 中标记它们。在 threshold1…

微服务架构---Ribbon\Feign

Ribbon(负载均衡) Ribbon概述 在 SpringCloud 中&#xff0c; Nacos⼀般配合Ribbon进行使用&#xff0c;Ribbon提供了客户端负载均衡的功能&#xff0c;Ribbon利用从Nacos中读取到的服务信息&#xff0c;在调用服务节点提供的服务时&#xff0c;会合理的进行负载。 Ribbon作…