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

news2024/9/24 19:17:38

目录

一、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=======");
        // 打印请求 URL
        log.info("URL            : {}",);
        // 打印描述信息
        log.info("BusinessName   : {}", );
        // 打印 Http method
        log.info("HTTP Method    : {}", );
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}", );
        // 打印请求的 IP
        log.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=======");
        // 打印请求 URL
        log.info("URL            : {}",request.getRequestURL());
        // 打印描述信息
        log.info("BusinessName   : {}",systemLog.businessName());
        // 打印 Http method
        log.info("HTTP Method    : {}",request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());
        // 打印请求的 IP
        log.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=======");
        // 打印请求 URL
        log.info("URL            : {}",request.getRequestURL());
        // 打印描述信息
        log.info("BusinessName   : {}",systemLog.businessName());
        // 打印 Http method
        log.info("HTTP Method    : {}",request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        log.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),((MethodSignature) joinPoint.getSignature()).getName());
        // 打印请求的 IP
        log.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.coloradmin.cn/o/1420658.html

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

相关文章

【美赛获奖必看】史上最强最全美赛论文模板,word+latex

大家好呀&#xff0c;现在是1.30日&#xff0c;距离2024美赛只有短短两天时间啦&#xff0c;众所周知&#xff0c;美赛对于论文排版是非常看重的&#xff0c;一个好的论文排版往往可以起到事半功倍的效果&#xff0c;而如果论文不美观&#xff0c;即便建模求解等都不错&#xf…

u盘文件防止拷贝的方法

在企业的日常运营中&#xff0c;U盘作为一种常用的移动存储设备&#xff0c;常常被用于数据的传输和备份。然而&#xff0c;U盘的不当使用也可能导致企业数据的泄露&#xff0c;给企业带来巨大的风险。 为了保护企业数据的安全&#xff0c;使用域智盾等软件工具进行U盘文件防拷…

【优选算法系列】【专题三二分查找】第二节.852. 山脉数组的峰顶索引和162. 寻找峰值

文章目录 前言一、山脉数组的峰顶索引 1.1 题目描述 1.2 题目解析 1.2.1 算法原理 1.2.2 代码编写 1.2.3 题目总结二、寻找峰值 2.1 题目描述 2.2 题目解析 2.2.1 算法原理 2.2.2 代码编写 …

【Coding】寒假每日一题Day.8. 超级胶水

题目来源 题目来自于AcWing平台&#xff1a;https://www.acwing.com/problem/content/2871/。 以blog的形式记录程序设计算法学习的过程&#xff0c;仅做学习记录之用。 题目描述 输入输出格式 范围 样例 思路 思路参考自闫总的视频题解。 与区间合并不同&#xff0c;此处…

为爱穿针 用冬日针爱温暖孩子——Home尧泰汉海慈善专项基金“益起暖冬”公益行动圆满结束

2024年1月15日&#xff0c;Home尧泰汉海四季有爱•益路向阳之“益起暖冬”公益行动圆满结束&#xff0c;本次公益活动由重庆市慈善总会、Home尧泰汉海慈善专项基金、重庆市渝中区红樱桃义工协会主办。 千丝万缕汇针爱 五湖四海显温情 用公益的针线编织爱的温暖 让孩子的冬日沐…

字符串转码-第11届蓝桥杯国赛Python真题精选

[导读]&#xff1a;超平老师的Scratch蓝桥杯真题解读系列在推出之后&#xff0c;受到了广大老师和家长的好评&#xff0c;非常感谢各位的认可和厚爱。作为回馈&#xff0c;超平老师计划推出《Python蓝桥杯真题解析100讲》&#xff0c;这是解读系列的第35讲。 字符串转码&#…

PawSQL更新 | 新增18个SQL性能审核重写规则

PawSQL最新版本针对DML和DQL新增了审核和重写优化规则共计33个&#xff0c;整体的规则数目达到了83个&#xff0c;覆盖了正确性&#xff0c;安全性、可维护性、性能四个方面的SQL质量问题&#xff0c;并提供了优化建议&#xff0c;已经形成比较完善的针对数据操作的SQL质量审查…

动态规划之买卖股票问题(篇三)(买卖股票的最佳时机)

本篇博客主要讲解309.最佳买卖股票时机含冷冻期和714.买卖股票的最佳时机含手续费&#xff0c;这也是股票系列的最后一篇讲解。 309.最佳买卖股票时机含冷冻期 题目&#xff1a; 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算…

SpringBoot---创建项目

介绍 此项目SpringBoot使用的是2.6.1版本&#xff0c;由于这个项目使用的是maven聚合方式创建的&#xff0c;所以第二步是我在聚合方式下需要添加的依赖&#xff0c;完整的pom.xml内容放到了最下面。 第一步&#xff1a;创建Maven项目 这个里什么也不勾选&#xff0c;直接点…

<网络安全>《9 入侵防御系统IPS》

1 概念 IPS&#xff08; Intrusion Prevention System&#xff09;是电脑网络安全设施&#xff0c;是对防病毒软件&#xff08;Antivirus Programs&#xff09;和防火墙&#xff08;Packet Filter, Application Gateway&#xff09;的补充。 入侵预防系统&#xff08;Intrusio…

PMP成绩查询及电子版证书下载

PMP项目管理--学习专栏https://blog.csdn.net/xmws_it/category_10954848.html?spm1001.2014.3001.5482 2023年11月25日PMP考试成绩今日凌晨开始发布&#xff0c;按照往年的情况&#xff0c;成绩都是分批出的&#xff0c;如果暂时没查到成绩的同学请耐心等待&#xff0c;预计…

【webrtc】m98 : vs2019 直接构建webrtc及moduletest工程 2

字数有限制,我们继续 【webrtc】m98 : vs2019 直接构建webrtc及unitest工程 1modules_unittests 构建 Build started... 1>------ Build started: Project: modules_unittests, Configuration: GN Win32 ------ 1>ninja: Entering directory `G:\CDN\rtcCli\m98\src\o…

Git怎样用?(下载到本地,和在本地初始化)

全局设置&#xff1a; 点击第二个 输入&#xff1a; 例如&#xff1b;邮箱是随意地 git config --global user.name "名字" git config --global user.email "邮箱" 获取git仓库 本地初始化&#xff1a; 创建仓库 右键第二个 输入 git init 克隆&#…

力扣hot100 子集 回溯 超简洁

Problem: 78. 子集 文章目录 思路复杂度Code 思路 &#x1f468;‍&#x1f3eb; 参考题解 复杂度 时间复杂度: 添加时间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) 空间复杂度: 添加空间复杂度, 示例&#xff1a; O ( n ) O(n) O(n) Code class Solution {List<Li…

wsl-ubuntu 安装 nginx

wsl-ubuntu 安装 nginx 1. 安装 nginx2. 确认 nginx 启动状态3. 重启 nginx4. 停止 nginx 1. 安装 nginx sudo apt install nginx2. 确认 nginx 启动状态 systemctl status nginx3. 重启 nginx systemctl restart nginx4. 停止 nginx systemctl stop nginx完成&#xff01;…

网络安全防御保护 Day4

要点一&#xff1a;防火墙的智能选路 就近选路&#xff1a; 在访问不同运营商的服务器时直接通过对应运营商的链路&#xff0c;以此来提高通信效率&#xff0c;避免绕路。 策略路由&#xff08;PBR&#xff09;&#xff1a; 这是一种基于用户定义的策略&#xff08;如业务需求、…

【云上建站】快速在云上构建个人网站1——概述及获取虚拟机

快速在云上构建个人网站1——概述及获取虚拟机 前言-个人建站一、云服务简介1、云服务2、优点首先&#xff0c;云服务器是按需租赁的&#xff0c;其次&#xff0c;云服务器公司会负责服务器的维护管理&#xff0c; 3、主流应用网站建设测试学习数据管理制图渲染 二、获取ECS1、…

PetaPixel专访尼康工程师 深度解读尼康相机的设计之道

值此尼克尔镜头成立90周年之际&#xff0c;国外知名媒体PetaPixel采访了尼康设计中心前川明哉&#xff08;Akiya Maekawa&#xff09;先生和尼康UX企划部&#xff08;影像事业部&#xff09;日野光輝&#xff08;Mitsuteru Hino&#xff09;先生&#xff0c;通过与他们的对话&a…

选型 之 工业相机篇

一、概述 23年24年行情不会好&#xff0c;公司各种想办法裁员&#xff0c;在大陆这个大熔炉中只能不断地提炼。我个人主要是在工业领域做2D图像算法和3D算法&#xff0c;但是现在出去都需要全能人才 方案、算法、运动控制等&#xff0c;我目前最大的短板就是方案&#xff0c;在…

力扣hot100 电话号码的字母组合 回溯

Problem: 17. 电话号码的字母组合 文章目录 思路复杂度&#x1f49d; Code 思路 &#x1f468;‍&#x1f3eb; 参考题解 复杂度 时间复杂度: O ( 3 8 ) O(3^8) O(38) 空间复杂度: O ( 3 8 ) O(3^8) O(38) &#x1f49d; Code class Solution {String[] map { "…