实际项目中的SpringAOP实现日志打印

目录

一、AOP实现日志

1.1 需求分析:

1.2 定义切面类和切点:

扩展:finally中的代码块一定会执行吗?

扩展 总结

1.3 定义环绕通知

1.4 handleBefore 的具体实现

1.4.1 获取url

 1.4.2 获取接口描述信息

 1.4.3 后续获取

1.5 handleAfter 的具体实现

1.6 实现效果

二、整体代码如下

2.1 自定义注解

 2.2 AOP切面、切点、环绕通知


之前虽然有简单学习AOP的相关知识,但是却一直未在项目中运用,借此博客巩固。

一、AOP实现日志

首先需要添加SpringAOP依赖:

去Maven仓库中找到Spring AOP的依赖(注意,寻找的其实是用于SpringBoot项目的SpringAOP依赖,如果是用于Spring项目的,Spirng自带的库中是已经添加过该依赖的):

代码如下: 

<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-aop -->
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId><version>2.7.10</version>
</dependency>

实际项目中演示如下:

1.1 需求分析:

需要通过日志记录接口调用信息。便于后期调试排查。并且可能有很多接口都需要进行日志的记录。

接口被调用时日志打印效果预览:

日志打印格式如下:

        log.info("=======Start=======");// 打印请求 URLlog.info("URL            : {}",);// 打印描述信息log.info("BusinessName   : {}", );// 打印 Http methodlog.info("HTTP Method    : {}", );// 打印调用 controller 的全路径以及执行方法log.info("Class Method   : {}.{}", );// 打印请求的 IPlog.info("IP             : {}",);// 打印请求入参log.info("Request Args   : {}",);// 打印出参log.info("Response       : {}", );// 结束后换行log.info("=======End=======" + System.lineSeparator());

1.2 定义切面类和切点:

注意,这里使用两个注解,分别为 @Component 和 @Aspect 注解。

@Component将这个类注入到Spring容器中

而@Aspect是告诉Spring容器这是一个切面类

@Component //注入容器
@Aspect //用于告诉spring容器这是一个切面类
public class LogAspect {// 切点@Pointcut("@annotation(com.fox.annotation.SystemLog)")public void pt() {}}

@Pointcut括号中,我们使用的是 自定义注解 的形式,而非切点表达式,实际项目开发中,自定义注解的方式居多,也更方便。

自定义注解如下,后续会添加属性。

package com.fox.annotation;import org.aspectj.lang.annotation.Around;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemLog {}

可以通过idea提供的方式: Copy Reference 直接将 自定义注解 的路径找出,写入上面代码所提供的 @annotation 的括号中。

定义环绕通知的时候,我们需要注意异常抛出的方式:不能使用try-catch方式捕获,而是需要直接throws 异常,因为我们需要让项目中的:统一异常处理来进行捕获,而不是自己对其进行捕获操作。

结合上,日志打印的要求,所以我们这里应当选择环绕通知:

所以我们这里通过快捷键:ctrl+alt+T,进行try-final操作:目的是实现我们项目需要的:最后打印。

既然聊到try-final,顺便就简单扩展一下:

扩展:finally中的代码块一定会执行吗?

答案是否定的,正常运行的情况下,finally中的代码是一定会执行的,但是,如果遇到以下异常情况,那么finally中的代码就不会继续执行了。

1.  程序在try块中遇到了System.exit() 方法,会立即终于程序的执行,这时finally块中的代码是不会被执行的,例如执行以下代码:

 运行结果如下:

2. 在try块中遇到 Runtime.getRuntime().halt() 代码,则会强制终止正在运行的JVM,与System.exit() 方法不同,此方法不会触发JVM关闭序列,因此,当我们调用halt方法时,都不会执行关闭钩子或终结器。实现代码如下:

 运行结果如下:

3.程序在 try 块中遇到无限循环或者发生死锁等情况时,程序可能无法正常跳出 try 块,此时 finally 块中的代码也不会被执行。
4. 掉电问题,程序还没有执行到 finally 就掉电了 (停电了),那 finally 中的代码自然也不
会执行。

5.JVM 异常崩溃问题导致程序不能继续执行,那么 finally 的代码也不会执行。

扩展 总结

正常运行的情况下,finally 中的代码是一定会执行的,但是,如果遇到 System.exit0)方法或 Runtime.getRuntime().halt()方法,或者是 try 中发生了死循、死锁,遇到了掉电JVM 崩溃等问题,那么 finally 中的代码也是不会执行的。

1.3 定义环绕通知

大体架构如下,下面我们会对方法内容进一步补充,

另外,这里的@Slf4j 注解是由Lombok提供的,用于提供日志打印的log对象。

@Component //注入容器
@Aspect //用于告诉spring容器这是一个切面类
@Slf4j
public class LogAspect {@Pointcut("@annotation(com.fox.annotation.SystemLog)")public void pt() {}@Around("pt()")public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {Object ret;try {handleBefore();ret = joinPoint.proceed();handleAfter();} finally {//结束后进行换行log.info("=======End=======" + System.lineSeparator());}return ret;}private void handleAfter() {}private void handleBefore() {}}

System.lineSeparator() 是一个系统的换行符,因为Linux和window之类的系统换行符是不同的。

1.4 handleBefore 的具体实现

1.4.1 获取url

思路分析:

由于打印的日志,第一个是需要获取url,那么我们其实就是要获取request对象,

RequestContextHolder.getRequestAttributes() 的返回类型是接口类型,不符合。

那么我们就看看这个 RequestAttributes 的实现类,ctrl+alt+鼠标左键查看:

查看这个类内部,发现里面有我们需要的request对象。

因此,我们可以对最先的代码进行改进,强转为 这个接口的实现类型 即可:

 1.4.2 获取接口描述信息

由于日志要求的是打印,该接口的信息,所以我们为自定义注解添加 businessName属性。

自定义注解更新如下:

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemLog {String businessName();
}

应用到实际接口中,所以日志中想要获取接口的信息,只要获取SystemLog这个对象即可:

实现代码如下,在getSystemLog中我们通过joinPoint方法进一步获取使用@SystemLog注解的方法信息,然后通过强转子接口的方法,反射等方法拿到SystemLog对象,进而实现打印该接口的信息。

 1.4.3 后续获取

代码实现: 

    private void handleBefore(ProceedingJoinPoint joinPoint) {ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = servletRequestAttributes.getRequest();// 获取被增强方法上的注解对象SystemLog systemLog = getSystemLog(joinPoint);log.info("=======Start=======");// 打印请求 URLlog.info("URL            : {}",request.getRequestURL());// 打印描述信息log.info("BusinessName   : {}",systemLog.businessName());// 打印 Http methodlog.info("HTTP Method    : {}",request.getMethod());// 打印调用 controller 的全路径以及执行方法log.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());// 打印请求的 IPlog.info("IP             : {}",request.getRemoteHost());// 打印请求入参log.info("Request Args   : {}", JSON.toJSONString(joinPoint.getArgs()));}

1.5 handleAfter 的具体实现

1.6 实现效果

访问带有自定义注解@SystemLog 的接口后,

控制台打印如下:

二、整体代码如下

2.1 自定义注解

package com.fox.annotation;import org.aspectj.lang.annotation.Around;import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface SystemLog {String businessName();
}

 2.2 AOP切面、切点、环绕通知

package com.fox.aspect;import com.alibaba.fastjson.JSON;
import com.fox.annotation.SystemLog;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
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.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;import javax.servlet.http.HttpServletRequest;@Component //注入容器
@Aspect //用于告诉spring容器这是一个切面类
@Slf4j
public class LogAspect {@Pointcut("@annotation(com.fox.annotation.SystemLog)")public void pt() {}@Around("pt()")public Object printLog(ProceedingJoinPoint joinPoint) throws Throwable {Object ret;try {handleBefore(joinPoint);ret = joinPoint.proceed();handleAfter(ret);} finally {//结束后进行换行log.info("=======End=======" + System.lineSeparator());}return ret;}private void handleBefore(ProceedingJoinPoint joinPoint) {ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();HttpServletRequest request = servletRequestAttributes.getRequest();// 获取被增强方法上的注解对象SystemLog systemLog = getSystemLog(joinPoint);log.info("=======Start=======");// 打印请求 URLlog.info("URL            : {}",request.getRequestURL());// 打印描述信息log.info("BusinessName   : {}",systemLog.businessName());// 打印 Http methodlog.info("HTTP Method    : {}",request.getMethod());// 打印调用 controller 的全路径以及执行方法log.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());// 打印请求的 IPlog.info("IP             : {}",request.getRemoteHost());// 打印请求入参log.info("Request Args   : {}", JSON.toJSONString(joinPoint.getArgs()));}private SystemLog getSystemLog(ProceedingJoinPoint joinPoint) {MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();SystemLog annotation = methodSignature.getMethod().getAnnotation(SystemLog.class);return annotation;}private void handleAfter(Object ret) {// 打印出参log.info("Response       : {}",JSON.toJSONString(ret));}}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/660156.shtml

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

相关文章

全球网络加速的5种方法,云桥通力推SDWAN企业组网

全球网络加速旨在通过采用多种技术和服务&#xff0c;提高全球范围内的网络连接速度和性能。在全球化发展的趋势下&#xff0c;跨国企业、云服务提供商和全球用户之间的网络通信变得日益关键。其目标是克服跨国网络连接中的延迟、带宽限制和数据包丢失等问题&#xff0c;以提供…

【C++】 C++入门—内联函数

C入门 1 内联函数1.1 定义1.2 查看方式1.3 注意 Thanks♪(&#xff65;ω&#xff65;)&#xff89;谢谢阅读下一篇文章见&#xff01;&#xff01;&#xff01; 1 内联函数 1.1 定义 程序在执行一个函数前需要做准备工作&#xff1a;要将实参、局部变量、返回地址以及若干寄存…

小米商城服务治理之客户端熔断器(Google SRE客户端熔断器)

目录 前言 一、什么是Google SRE熔断器 二、Google SRE 熔断器的工作流程&#xff1a; 三、客户端熔断器 (google SRE 熔断器) golang GRPC 实现 四、客户端熔断器 (google SRE 熔断器) golang GRPC单元测试 大家可以关注个人博客&#xff1a;xingxing – Web Developer …

【C++干货基地】C++引用与指针的区别:深入理解两者特性及选择正确应用场景

&#x1f3ac; 鸽芷咕&#xff1a;个人主页 &#x1f525; 个人专栏: 《C干货基地》《粉丝福利》 ⛺️生活的理想&#xff0c;就是为了理想的生活! 引入 哈喽各位铁汁们好啊&#xff0c;我是博主鸽芷咕《C干货基地》是由我的襄阳家乡零食基地有感而发&#xff0c;不知道各位的…

Log4j2-11-log4j2 Layout 布局入门介绍

Layout 布局 Appender使用Layout将LogEvent格式化为一种表单&#xff0c;以满足将要消费日志事件的任何需求。 在Log4j中。x和Logback布局被期望将事件转换为字符串。 在Log4j 2布局返回一个字节数组。这使得Layout的结果可以在更多类型的appender中使用。然而&#xff0c;这…

[机器学习]简单线性回归——最小二乘法

一.线性回归及最小二乘法概念 2.代码实现 # 0.引入依赖 import numpy as np import matplotlib.pyplot as plt# 1.导入数据 points np.genfromtxt(data.csv, delimiter,) # points[0,0]# 提取points中的两列数据&#xff0c;分别作为x&#xff0c;y x points[:, 0] y poi…

Netty源码三:NioEventLoop创建与run方法

1.入口 会调用到父类SingleThreadEventLoop的构造方法 2.SingleThreadEventLoop 继续调用父类SingleThreadEventExecutor的构造方法 3.SingleThreadEventExecutor 到这里完整的总结一下&#xff1a; 将线程执行器保存到每一个SingleThreadEventExcutor里面去创建了MpscQu…

安卓滚动视图ScrollView

<?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"match_parent"android:orientatio…

docker镜像命令

docker images 列表本机上的镜像 - REPOSITORY&#xff1a;表示镜像的仓库源 - TAG&#xff1a;镜像的标签 - IMAGE ID&#xff1a;镜像 - ID CREATED&#xff1a;镜像创建时间 - SIZE&#xff1a;镜像大小 同一仓库源可以有多个 TAG&#xff0c;代表这个仓库源的不同个版本&am…

大洋钻探系列之七中国大洋钻探船梦想号

中国大洋钻探梦想号2021年11月30日开工建造&#xff0c;2023年12月27日在珠江口海域完成首航&#xff0c;预计2024年正式交付使用&#xff0c;从而实现了2011年中国IODP专家咨询委员会提出的我国大洋钻探发展“三步走”战略的第三步建造中国的大洋钻探船。 恰逢IODP新旧计划交替…

vue3 + vite:打包部署后,动态组件渲染404问题解决

问题描述: 当需要渲染动态组件,动态的组件路径配置在数据库中时,如下图,本地运行能正常访问,用vite打包部署后,生产上改路径为404. 起初认为是,vite打包后的文件都是.js, 当页面加载后从数据库拿来的路径是.vue, 并且是src/xxx/xxx.vue 这种绝对路径形式的,所以就找不…

【每日一题】 2024年1月汇编

&#x1f525;博客主页&#xff1a; A_SHOWY&#x1f3a5;系列专栏&#xff1a;力扣刷题总结录 数据结构 云计算 数字图像处理 力扣每日一题_ 【1.4】2397.被列覆盖的最多行数 2397. 被列覆盖的最多行数https://leetcode.cn/problems/maximum-rows-covered-by-columns/ 这…

哪个牌子的头戴式耳机好?推荐性价比高的头戴式耳机品牌

随着科技的不断发展&#xff0c;耳机市场也呈现出百花齐放的态势&#xff0c;从高端的奢侈品牌到亲民的平价品牌&#xff0c;各种款式、功能的耳机层出不穷&#xff0c;而头戴式耳机作为其中的一员&#xff0c;凭借其优秀的音质和降噪功能&#xff0c;受到了广大用户的喜爱&…

ArrayList在添加元素时报错java.lang.ArrayIndexOutOfBoundException

一、添加单个元素数组越界分析 add源码如下 public boolean add(E e) {ensureCapacityInternal(size 1); // Increments modCount!!elementData[size] e;return true; } size字段的定义 The size of the ArrayList (the number of elements it contains). ArrayList的大…

雷达DoA估计的跨行业应用--麦克风阵列声源定位(Matlab仿真)

一、概述 麦克风阵列&#xff1a; 麦克风阵列是由一定数目的声学传感器&#xff08;麦克风&#xff09;按照一定规则排列的多麦克风系统&#xff0c;而基于麦克风阵列的声源定位是指用麦克风拾取声音信号&#xff0c;通过对麦克风阵列的各路输出信号进行分析和处理&#xff0c;…

力扣hot100 跳跃游戏 贪心

Problem: 55. 跳跃游戏 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考 挨着跳&#xff0c;记录最远能到达的地方 复杂度 时间复杂度: O ( n ) O(n) O(n) 空间复杂度: O ( 1 ) O(1) O(1) Code class Solution {public boolean canJump(int[] nums)…

7 STL

1、STL简介 1.1基本概念 可复用利用的东西&#xff01; 面向对象和泛型编程&#xff08;模板&#xff09;的 目的->提升复用性 为了建立数据结构和算法的一套标准->STL横空出世 STL(Standard Template Liberary)标准模板库广义分&#xff1a;容器、算法、迭代器容器…

lwIP 初探(第一节)

一、TCP/IP 协议栈架构 网络协议有很多&#xff0c;如 MQTT、TCP、UDP、IP 等协议&#xff0c;这些协议组成了 TCP/IP 协议栈&#xff0c; 同时&#xff0c;这些协议具有层次性&#xff0c;它们分布在应用层&#xff0c;传输层和网络层。TCP/IP 协议栈的分层结 构和网络协议得…

百无聊赖之JavaEE从入门到放弃(十五)包装类

目录 一.包装类概念 二.自动装箱和拆箱 三.包装类的缓存问题 一.包装类概念 基本数据类型的包装类 我们前面学习的八种基本数据类型并不是对象&#xff0c;为了将基本类型数据和对象之间实现互 相转化&#xff0c;Java 为每一个基本数据类型提供了相应的包装类。 Java 是…

八斗学习笔记

1 初始环境安装 Anaconda安装(一款可以同时创建跟管理多个python环境的软件) https://blog.csdn.net/run_success/article/details/134656460 Anaconda创建一个新python环境(安装人工智能常用的第三方python包&#xff0c;如&#xff1a;tensorflow、keras、pytorch) https://…