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.rhkb.cn/news/429149.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SkyWalking 环境搭建部署

架构简介 skywalking agent : 和业务系统绑定在一起,负责收集各种监控数据skywalking oapservice : 是负责处理监控数据的,比如接受skywalking agent的监控数据,并存储在数据库中;接受skywalking webapp的前端请求,从数据库查询数据,并返回数据给前端。Skywalking oapserv…

华为HarmonyOS地图服务 7- 在地图上绘制标记

场景介绍 本章节将向您介绍如何在地图的指定位置添加标记以标识位置、商家、建筑等。 点标记用来在地图上标记任何位置,例如用户位置、车辆位置、店铺位置等一切带有位置属性的事物。Map Kit提供的点标记功能(又称 Marker)封装了大量的触发事件,例如点击事件、长按事件、…

【RabbitMQ】消息分发、事务

消息分发 概念 RabbitMQ队列拥有多个消费者时&#xff0c;队列会把收到的消息分派给不同的消费者。每条消息只会发送给订阅该队列订阅列表里的一个消费者。这种方式非常适合扩展&#xff0c;如果现在负载加重&#xff0c;那么只需要创建更多的消费者来消费处理消息即可。 默…

linux网络编程5

24.9.21学习目录 一.TCP1.TCP流程2.TCP相关函数3.三次握手 一.TCP 1.TCP流程 服务器流程&#xff1a; 创建套接字socket&#xff08;&#xff09;将套接字与服务器网络信息结构体绑定bind&#xff08;&#xff09;将套接字设置为监听状态listen&#xff08;&#xff09;阻塞等…

进程间的通信4 共享内存

共享内存 1.共享内存简介 共享内存是将分配的物理空间直接映射到进程的用户虚拟地址空间中&#xff0c;减少数据在内核空间缓存共享内存是一种效率较高的进程间通讯的方式在 Linux 系统中通过 ipcs -m 查看所有的共享内存 共享内存模型图 2.共享内存的创建 1.函数头文件 #…

Java算法专栏

专栏导读 在当今这个技术日新月异的时代&#xff0c;Java算法作为软件开发的核心&#xff0c;对于提升程序性能和解决复杂问题至关重要。本“Java算法”专栏旨在帮助读者深入理解Java编程语言中的算法原理和应用&#xff0c;通过实战案例和深入分析&#xff0c;使读者能够掌握…

Java汽车销售管理

技术架构&#xff1a; springboot mybatis Mysql5.7 vue2 npm node 功能描述&#xff1a; 针对汽车销售提供客户信息、车辆信息、订单信息、销售人员管理、财务报表等功能&#xff0c;提供经理和销售两种角色进行管理 效果图&#xff1a;

Python基础学习(3)

目录 一&#xff0c;函数 1&#xff0c;函数的定义 2&#xff0c;函数的参数 1&#xff0c;默认值 2&#xff0c;传参 3&#xff0c;返回值 4&#xff0c;变量的作用域 5&#xff0c;函数的调用 二&#xff0c;常用数据结构 1&#xff0c;列表 列表的定义 列表的特性…

【Geoserver使用】REST API调用(工作空间部分)

文章目录 前言一、Geoserver REST API(GeoServer Workspace)二、GeoServer Workspace接口使用1.GET请求 /workspaces2.POST请求 /workspaces3.GET请求 /workspaces/{workspaceName}4.PUT /workspaces/{workspaceName}5.DELETE /workspaces/{workspaceName} 总结 前言 根据Geos…

C++ | Leetcode C++题解之第423题从英文中重建数字

题目&#xff1a; 题解&#xff1a; class Solution { public:string originalDigits(string s) {unordered_map<char, int> c;for (char ch: s) {c[ch];}vector<int> cnt(10);cnt[0] c[z];cnt[2] c[w];cnt[4] c[u];cnt[6] c[x];cnt[8] c[g];cnt[3] c[h] - …

YOLOv10 简介

YOLOv10&#xff0c;由清华大学的研究人员基于 Ultralytics Python 包构建&#xff0c;引入了一种全新的实时目标检测方法&#xff0c;该方法解决了以往 YOLO 版本中后处理和模型架构方面的不足。通过消除非极大值抑制&#xff08;NMS&#xff09;并优化各种模型组件&#xff0…

【解决】chrome 谷歌浏览器,鼠标点击任何区域都是 Input 输入框的状态,能看到输入的光标

chrome 谷歌浏览器&#xff0c;鼠标点击任何区域都是 Input 输入框的状态&#xff0c;能看到输入的光标 今天打开电脑的时候&#xff0c;网页中任何文本的地方&#xff0c;只要鼠标点击&#xff0c;就会出现一个输入的光标&#xff0c;无论在哪个站点哪个页面都是如此。 我知道…

十四、运算放大电路

运算放大电路 1、理想运算放大器的概念。运放的输入端虚拟短路、虚拟断路之间的区别; 2、反相输入方式的运放电路的主要用途&#xff0c;以及输入电压与输出电压信号的相位 3、同相输入方式下的增益表达式(输入阻抗、输出阻抗)

Redis-01 入门和十大数据类型

Redis支持两种持久化方式&#xff1a;RDB持久化和AOF持久化。 1.RDB持久化是将Redis的数据以快照的形式保存在磁盘上&#xff0c;可以手动触发或通过配置文件设置定时触发。RDB保存的是Redis在某个时间点上的数据快照&#xff0c;可以通过恢复RDB文件来恢复数据。 2.AOF持久化…

55. QTableWidget的基本使用

1. 说明 在软件界面开发中,基本上离不开数据的展示以供客户查看一些比较关注的信息,比如公司做一个员工个人信息管理系统,需要一个界面能够展示员工个人基本信息,实现这种效果可以采用多种形式,其中比较简单的一种是使用QT提供的QTableWidget控件,这个控件已经封装了一些…

LeetCode 面试经典150题 190.颠倒二进制位

复习知识&#xff1a;正数的原码、反码、补码相同&#xff0c;负数的反码在其原码的基础上, 符号位不变&#xff0c;其余各个位取反&#xff0c;负数的补码是在其原码的基础上, 符号位不变, 其余各位取反, 最后1 (即在反码的基础上1)。 题目&#xff1a;颠倒给定的 32 位无符号…

Springboot3 + MyBatis-Plus + MySql + Uniapp 商品加入购物车功能实现(最新教程附源码)

Springboot3 MyBatis-Plus MySql Uniapp 商品加入购物车功能实现&#xff08;针对上一篇sku&#xff09; 1、效果展示2、后端代码2.1 model2.2 mapper server serverImpl 参照上一篇自动生成2.3 controller 3、前端代码3.1 index.js3.2 shop-info.vue3.3 ShopBottomButton.v…

计算机毕业设计hadoop+spark+hive新能源汽车销售数据分析系统 二手车销量分析 新能源汽车推荐系统 可视化大屏 汽车爬虫 机器学习

《HadoopSparkHive新能源汽车销售数据分析系统》开题报告 一、选题背景与意义 1.1 选题背景 随着全球对环境保护意识的增强和能源结构的转型&#xff0c;新能源汽车市场迅速崛起。新能源汽车的销售数据不仅反映了市场趋势和消费者偏好&#xff0c;还为企业决策、政府监管和政…

【玉米田】

题目 代码 #include <bits/stdc.h> using namespace std; typedef long long LL;const int mod 1e8; const int M 1 << 12; LL f[13][M]; int g[13]; vector<int> state; vector<int> p[M]; int n, m; bool check(int x) {return !(x & x <&…

“一屏显江山”,激光显示重构「屏中世界」

【潮汐商业评论/原创】 2024年国庆期间&#xff0c;曾感动过无数国人的舞蹈诗剧《只此青绿》改编的同名电影即将上映&#xff0c;而这一次观众们不必走进电影院&#xff0c;在家里打开官方合作的海信激光电视也能享受到同等的视听效果&#xff0c;这是激光电视在观影场景领域的…