SpringCloud整合AOP做日志管理

news2024/11/13 22:31:06

目录

  • 1、前置知识
  • 2、步骤
    • 2.1、依赖
    • 2.2、自定义注解,用于注解式AOP
    • 2.3、定制切面类
    • 2.4、测试

1、前置知识

  • 切面(Aspect):官方的抽象定义为“一个关注点的模块化,这个关注点可能会横切多个对象”,在本例中,“切面”就是类LogAspect所关注的具体行为,例如,TestServiceImp.update()的调用就是切面LogAspect所关注的行为之一。“切面”在ApplicationContext中aop:aspect来配置,此项目中spring-boot-starter-aop已包含配置。
  • 连接点(Joinpoint) :程序执行过程中的某一行为,例如,ILogService.insert的调用或者ILogService.delete抛出异常等行为。
  • 通知(Advice) :“切面”对于某个“连接点”所产生的动作,例如,TestAspect中对com.spring.service包下所有类的方法进行日志新增的动作就是一个Advice。其中,一个“切面”可以包含多个“Advice”,新增,修改,删除等。
  • 切入点(Pointcut) :匹配连接点的断言,在AOP中通知和一个切入点表达式关联。大部分做法都由切入点表达式execution(* com.spring.service..(…))来决定,本例是通过@Pointcut("@annotation(com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger.UserOptLogger) ")注解的方式。
    其实我感觉,切入点就是将切面类和注解绑定起来(如果是基于注解的AOP的话)。
  • 目标对象(Target Object) :被一个或者多个切面所通知的对象。例如,AServcieImpl和BServiceImpl,当然在实际运行时,Spring AOP采用代理实现,实际AOP操作的是TargetObject的代理对象。
  • AOP代理(AOP Proxy) :在Spring AOP中有两种代理方式,JDK动态代理和CGLIB代理①。默认情况下,TargetObject实现了接口时,则采用JDK动态代理,例如,AServiceImpl;反之,采用CGLIB代理,例如,BServiceImpl。强制使用CGLIB代理需要将 aop:config的 proxy-target-class属性设为true。
  • 通知(Advice)类型
    前置通知(Before advice):在某连接点(JoinPoint)之前执行的通知,但这个通知不能阻止连接点前的执行。ApplicationContext中在aop:aspect里面使用aop:before元素进行声明。例如,LogAspect中的before方法。
    后置通知(After advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。ApplicationContext中在aop:aspect里面使用aop:after元素进行声明。例如,LogAspect中的after方法,所以调用doError抛出异常时,after方法仍然执行。
    返回后通知(After return advice):在某连接点正常完成后执行的通知,不包括抛出异常的情况。ApplicationContext中在aop:aspect里面使用元素进行声明。
    环绕通知(Around advice):包围一个连接点的通知,类似Web中Servlet规范中的Filter的doFilter方法。可以在方法的调用前后完成自定义的行为,也可以选择不执行。ApplicationContext中在aop:aspect里面使用aop:around元素进行声明。例如,LogAspect中的handleAround方法。
    抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。ApplicationContext中在aop:aspect里面使用aop:after-throwing元素进行声明。例如,LogAspect中的doAfterThrowing方法。

2、步骤

2.1、依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.2、自定义注解,用于注解式AOP

我们要自定义一个注解,干什么用呢?用于标注在需要进行AOP管理的对象上,这个对象可以是方法(ElementType.METHOD),也可以是其他的。但既然是注解式AOP,就都需要标注我们自定义的注解才能告诉spring,咱们要对它进行AOP管理。

package com.lyy.yingwudemo.yingwu_auth.annotion;

import com.lyy.yingwuDemo.yingwu_common.enums.LogType;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @author :lyy
 * @DESCRIPTION : 自定义注解,注解式AOP。用于用户在认证服务器的行为日志记录,比如登录登出注册等等。
 * @date : 04-18-21:06
 */
// 定义注解作用的范围,这里是方法
@Target(ElementType.METHOD)
// 定义注解生命周期,这里是运行时
@Retention(RetentionPolicy.RUNTIME)
public @interface UserOptLogger {
    /**
     * 业务名称
     */
    public String operation() default "";

    /**
     * 日志级别
     *
     * @return
     */
    public LogType level() default LogType.INFO;

    /**
     * 是否将当前日志记录到数据库中
     */
    public boolean save() default true;
}

package com.lyy.yingwuDemo.yingwu_common.enums;

/**
 * @author :lyy
 * @DESCRIPTION : 日志级别枚举类
 * @date : 04-18-21:11
 */
public enum LogType {
    INFO,WARN,ERROR;
}

2.3、定制切面类

  • 使用@Aspect注解将一个java类定义为切面类。干什么用呢?切面类其实就是用于定制我们需要在操作执行前后做的事情。相当于对多个业务功能横着切开做了统一的管理。有点抽象,看下代码就了然了。
package com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * @author :lyy
 * @DESCRIPTION : UserOptLogger切面类
 * @date : 04-20-20:41
 */
@Aspect
@Component
public class UserOptLoggerAspect {
    private static final Logger log = LoggerFactory.getLogger(UserOptLoggerAspect.class);
    /**
     * 切入点
     */
    @Pointcut("@annotation(com.lyy.yingwudemo.yingwu_auth.annotion.OptionLogger.UserOptLogger)")
    public void entryPoint() {
        // 无需内容
    }

    @Before("entryPoint()")
    public void before(JoinPoint joinPoint) {

        log.info("=====================开始执行前置通知==================");
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operation = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operation = method.getAnnotation(UserOptLogger.class).operation();// 操作人
                        break;
                    }
                }
            }
            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }

            // *========控制台输出=========*//
            log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:"
                    + paramsBuf.toString());
            log.info("=====================执行前置通知结束==================");
        } catch (Throwable e) {
            log.info("around " + joinPoint + " with exception : " + e.getMessage());
        }

    }

    @After("entryPoint()")
    public void after(JoinPoint joinPoint) {

        log.info("=====================开始执行后置通知==================");
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operation = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operation = method.getAnnotation(UserOptLogger.class).operation();// 操作人
                        break;
                    }
                }
            }
            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }

            // *========控制台输出=========*//
            log.info("[X用户]执行了[" + operation + "],类:" + targetName + ",方法名:" + methodName + ",参数:"
                    + paramsBuf.toString());
            log.info("=====================执行后置通知结束==================");
        } catch (Throwable e) {
            log.info("around " + joinPoint + " with exception : " + e.getMessage());
        }

    }
    /**
     * 环绕通知处理处理
     *
     * @param point
     * @throws Throwable
     */
    @Around("entryPoint()")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        // 先执行业务,注意:业务这样写业务发生异常不会拦截日志。
        Object result = point.proceed();
        try {
            handleAround(point);// 处理日志
        } catch (Exception e) {
            log.error("日志记录异常", e);
        }
        return result;
    }

    /**
     * around日志记录
     *
     * @param point
     * @throws SecurityException
     * @throws NoSuchMethodException
     */
    public void handleAround(ProceedingJoinPoint point) throws Exception {
        Signature sig = point.getSignature();
        MethodSignature msig = null;
        if (!(sig instanceof MethodSignature)) {
            throw new IllegalArgumentException("该注解只能用于方法");
        }
        msig = (MethodSignature) sig;
        Object target = point.getTarget();
        Method currentMethod = target.getClass().getMethod(msig.getName(), msig.getParameterTypes());
        // 方法名称
        String methodName = currentMethod.getName();
        // 获取注解对象
        UserOptLogger aLog = currentMethod.getAnnotation(UserOptLogger.class);
        // 类名
        String className = point.getTarget().getClass().getName();
        // 方法的参数
        Object[] params = point.getArgs();

        StringBuilder paramsBuf = new StringBuilder();
        for (Object arg : params) {
            paramsBuf.append(arg);
            paramsBuf.append("&");
        }
        // 处理log。。。。
        log.info("[X用户]执行了[" + aLog.operation() + "],类:" + className + ",方法名:" + methodName + ",参数:"
                + paramsBuf.toString());

    }

    @AfterThrowing(pointcut = "entryPoint()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        // 通过request获取登陆用户信息
        // HttpServletRequest request = ((ServletRequestAttributes)
        // RequestContextHolder.getRequestAttributes()).getRequest();
        try {
            String targetName = joinPoint.getTarget().getClass().getName();
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            Object[] arguments = joinPoint.getArgs();
            Class<?> targetClass = Class.forName(targetName);
            Method[] methods = targetClass.getMethods();
            String operation = "";
            for (Method method : methods) {
                if (method.getName().equals(methodName)) {
                    Class<?>[] clazzs = method.getParameterTypes();
                    if (clazzs.length == arguments.length) {
                        operation = method.getAnnotation(UserOptLogger.class).operation();
                        break;
                    }
                }
            }

            StringBuilder paramsBuf = new StringBuilder();
            for (Object arg : arguments) {
                paramsBuf.append(arg);
                paramsBuf.append("&");
            }

            log.info("异常方法:" + className + "." + methodName + "();参数:" + paramsBuf.toString() + ",处理了:" + operation);
            log.info("异常信息:" + e.getMessage());
        } catch (Exception ex) {
            log.error("异常信息:{}", ex.getMessage());
        }
    }
}

2.4、测试

只要在需要测试的方法上加上咱们自定义的注解就好了~,会在该方法执行前后执行切面中相应的方法。

@Controller
public class UserController {
    @UserOptLogger(operation = "用户登录")
    @RequestMapping("/tologin")
    public String tologin(){
        return "login";
    }
}

在这里插入图片描述

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

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

相关文章

超详细Redis入门教程——Redis命令(上)

前言 本文小新为大家带来 超详细Redis入门教程——Redis命令&#xff08;上&#xff09; 相关知识&#xff0c;具体内容包括Redis 基本命令&#xff0c;Key 操作命令&#xff0c;String 型 Value 操作命令&#xff0c;Hash 型 Value 操作命令&#xff0c;List 型 Value 操作命令…

快速搭建外卖配送服务:利用外卖系统源码实现

外卖配送服务已经成为了现代消费者生活的一部分&#xff0c;它不仅方便了消费者的用餐需求&#xff0c;也给商家提供了新的销售渠道&#xff0c;同时也为外卖配送员提供了更多的就业机会。为了满足这个市场的需求&#xff0c;外卖系统源码应运而生。 外卖系统源码是一个集成了…

第一章:数、式、方程与方程组

1.实数 1.内容概述 1.了解实数分类2.数轴3.相反数和倒数4.绝对值5.算数平方根相关概念及有关计算2.实数分类 3.实数的基本概念 1.数轴:规定原点、正方向和单位长度的直线叫做数轴2.相反数:绝对值相同而符号相反的两个数,互称相反数3.倒数:1除以任何数的商,我们叫做倒数,0…

超市购物系统【GUI/Swing+MySQL】(Java课设)

系统类型 Swing窗口类型Mysql数据库存储数据 使用范围 适合作为Java课设&#xff01;&#xff01;&#xff01; 部署环境 jdk1.8Mysql8.0Idea或eclipsejdbc 运行效果 本系统源码地址&#xff1a;https://download.csdn.net/download/qq_50954361/87682510 更多系统资源库…

Jenkins ssh windows 部署 java程序

版权说明&#xff1a; 本文由博主keep丶原创&#xff0c;转载请保留该段内容在文章头部。 原文地址&#xff1a; https://blog.csdn.net/qq_38688267/article/details/130203785 文章目录 前言实现步骤1. windows下载安装ssh2. windows 安装 winsw2.1 下载 winsw2.2 配置winsw2…

Linux 0.11启动过程分析(一)

Linux 0.11 系列文章 Linux 0.11启动过程分析&#xff08;一&#xff09; Linux 0.11 fork 函数&#xff08;二&#xff09; Linux0.11 缺页处理&#xff08;三&#xff09; Linux0.11 根文件系统挂载&#xff08;四&#xff09; Linux0.11 文件打开open函数&#xff08;五&…

[oeasy]python0132_变量含义_meaning_声明_declaration_赋值_assignment

变量定义 回忆上次内容 上次回顾了一下历史 python 是如何从无到有的看到 Guido 长期的坚持和努力 编程语言的基础都是变量声明 python是如何声明变量的呢&#xff1f; 变量 想要定义变量首先明确什么是变量 变量就是数值能变的量英文名称 variable 计算机在内存中分配出…

SpringBoot Starter 作用及原理

本文会以 mybatis 为例&#xff0c;通过对比 mybatis-spring 和 mybatis-spring-boot-starter 代码示例&#xff0c;了解 Starter 的作用。并对 mybatis-spring-boot-starter 进行简单剖析&#xff0c;了解 Starter 原理。 下面还有投票&#xff0c;一起参与进来吧&#x1f44d…

DataEase看中国 - 中国影星“成龙”电影票房数据分析

背景介绍 说起成龙&#xff0c;我们并不陌生&#xff0c;著名的动作明星。以武打动作片出道&#xff0c;凭借动作片《红番区》打入好莱坞&#xff0c;该片打破北美外语片票房纪录。 目前&#xff0c;由成龙、郭麒麟等主演的新片《龙马精神》正在公映&#xff0c;电影《…

【每日一练】JAVA算法求柱状图中最大的矩形面积

文章目录 前言题目分析算法实战1、创建算法方法2、创建测试用例3、查看测试结果 写在最后 前言 作为一名以JAVA语言为主的搬砖人&#xff0c;学习掌握好函数语法很重要&#xff0c;但是算法也是需要掌握的。今天我们就分享一个求柱状图中最大的矩形面积的题目&#xff0c;这个…

torch.utils.data.DataLoader中的next(iter(train_dataloader))

在做实验时&#xff0c;我们常常会使用用开源的数据集进行测试。而Pytorch中内置了许多数据集&#xff0c;这些数据集我们常常使用DataLoader类进行加载。 如下面这个我们使用DataLoader类加载torch.vision中的FashionMNIST数据集。 from torch.utils.data import DataLoader …

数据结构入门(C语言)顺序表的增删查改

目录 前言1. 顺序表的概念2. 动态顺序表2.1 顺序表的初始化与销毁2.2 顺序表的尾插容量检查2.3 顺序表的尾删2.4 顺序表的头插2.5 顺序表的头删2.6 固定位置的插入2.7 固定位置的删除2.8 查找和打印2.9 修改元素主函数部分(菜单) 结语 前言 本章会用C语言来描述数据结构中的顺…

协同运力、算力、存力,加速迈向智能世界

2023年4月20日&#xff0c;华为在HAS2023期间举办“迈向智能世界”主题论坛&#xff0c;吸引了来自全球的分析师、专家学者及媒体与会。会上&#xff0c;华为ICT战略与Marketing总裁彭松发表了“持续技术创新&#xff0c;加速迈向智能世界”的主题演讲。 华为ICT战略与Marketin…

zabbix监控linux主机

1.本实验使用centos7主机&#xff0c;IP地址为10.1.60.115&#xff0c;firewalld和selinux服务已关闭 2.下载zabbix yum源(与zabbix server用一样的版本) rpm -Uvh https://repo.zabbix.com/zabbix/5.0/rhel/7/x86_64/zabbix-release-5.0-1.el7.noarch.rpm 3.安装zabbix客户…

玛雅水上乐园|玩趣系列作品集

玛雅水上乐园曾经是一座历史悠久的玛雅金字塔&#xff0c;曾用于宗教和水上航行&#xff0c;被废弃了 3000 多年。现在&#xff0c;01a1 工作室已将其改造成一个令人兴奋的旅游景点&#xff0c;在这里你可以享受美食和饮料&#xff0c;享受日光浴&#xff0c;并结交新朋友。所以…

从零学习SDK(8)SDK的集成和部署

选择使用SDK与其他平台和服务进行集成和部署的好处有&#xff1a; 简化开发流程&#xff0c;节省时间和成本&#xff0c;无需从零开始编写复杂的代码逻辑。 保证功能的稳定性和兼容性&#xff0c;避免出现各种潜在的错误和问题。 享受SDK提供方的技术支持和更新&#xff0c;获…

L频段GaN功率放大器的设计关键点

氮化镓技术的不断进步促使设备在更高的功率、电源电压和频率下工作。 ​图1 QPD1013 晶体管的照片 如图1所示&#xff0c; QPD1013晶体管采用0.50 μm GaN-on-SiC技术。它采用具有成本效益的6.6x7.2 mm DFN(双边扁平无引脚)封装&#xff0c;与传统的金属陶瓷封装相比&#xff…

ROS学习——rotors仿真下载与运行

rotors 无人机仿真主要分为两类&#xff1a;硬件在环仿真&#xff08;HITL&#xff09;和软件在环仿真&#xff08;SITL全称Software in the loop&#xff09;。 无人机软件在环仿真是指完全用计算机来模拟出无人机飞行时的状态&#xff0c;而硬件在环仿真是指计算机连接飞控…

【ArcGIS Pro二次开发】(22):生成分级用地编码和名称

在国土空间规划中&#xff0c;用地用海分类采用三级分类体系&#xff0c;共设置24种一级类、106种二级类及39 种三级类。在某些场景中&#xff0c;需要按等级归类并汇总统计。 这个小工具的作用就是通过用地编码生成三级地类&#xff0c;作为后续统计的基础。 一、要实现的功能…

研读Rust圣经解析——Rust learn-3(变量与可变性,数据类型)

研读Rust圣经解析——Rust learn-3&#xff08;变量与可变性&#xff0c;数据类型&#xff09; 变量|常量与可变性变量声明案例为什么不可变变量可变&#xff08;mut关键字&#xff09;变量可变&#xff08;覆盖&#xff09; 常量声明 数据类型标量类型整型整型字面值整型溢出问…