SpringBoot教程(二十四) | SpringBoot集成日志AOP切面

news2024/11/15 11:02:23

SpringBoot教程(二十四) | SpringBoot集成日志AOP切面

  • (一)AOP 概要
    • 1. 什么是 AOP ?
    • 2. 为什么要用 AOP?
    • 3. AOP一般用来干什么?
    • 4. AOP 的核心概念
  • (二)Spring AOP
    • 1. 简述
    • 2. 相关注解
    • 3. @Pointcut 切入点的不同表达式 示例
      • 1. execution 表达式
      • 2. within 表达式
      • 3. this 和 target 表达式
      • 4. args 表达式
      • 5. @annotation、@within、@target 和 @args 表达式
  • (三)开始使用
    • 1. 引入AOP依赖
    • 2. 自定义注解
    • 3. 切面类
    • 4. 测试一(切入点用execution 表达式)
    • 5. 测试二(切入点用自定义注解方式)

(一)AOP 概要

1. 什么是 AOP ?

AOP是Aspect Oriented Programming的缩写,意为面向切面编程
这是一种通过预编译方式和运行期间动态代理实现程序功能统一维护的技术。
相比传统的面向对象编程(OOP),AOP更关注于将系统中的公共功能(如日志记录、安全控制、事务处理、异常处理等)从业务逻辑中分离出来,形成独立的模块,以便在不影响业务逻辑代码的情况下,对这些公共功能进行集中管理和维护。

2. 为什么要用 AOP?

  1. 降低耦合度:通过将公共功能从业务逻辑中分离出来,AOP可以显著降低系统各模块之间的耦合度,提高系统的可维护性和可扩展性。
  2. 提高代码复用性:公共功能的独立化使得这些功能可以在多个地方重复使用,而无需在每个业务逻辑中都重复编写相同的代码。
  3. 便于集中管理:AOP允许开发者将系统中的公共功能集中管理,便于统一维护和升级。
  4. 提高开发效率:通过使用AOP,开发者可以更加专注于业务逻辑的实现,而无需花费过多时间在公共功能的编写和维护上。

3. AOP一般用来干什么?

  1. 日志记录:在方法调用前后记录日志信息,帮助开发者进行性能分析和故障排查。
  2. 安全控制:在方法调用前进行权限检查,确保只有具有相应权限的用户才能执行该方法。
  3. 事务管理:在方法调用前后管理事务的开启、提交和回滚,确保数据的一致性和完整性。
  4. 异常处理:在方法调用过程中捕获并处理异常,提供友好的错误消息给用户。
  5. 性能监控:对方法调用的性能进行监控和分析,帮助开发者优化系统性能。
  6. 缓存优化:通过缓存方法调用的结果来提高系统性能,减少不必要的计算和资源消耗。

4. AOP 的核心概念

名词概念理解
通知(Advice)拦截到连接点之后所要执行的代码,通知分为前置、后置、异常、最终、环绕通知五类我们要实现的功能,如日志记录,性能统计,安全控制,事务处理,异常处理等等,说明什么时候要干什么
连接点(Joint Point)被拦截到的点,如被拦截的方法、对类成员的访问以及异常处理程序块的执行等等,自身还能嵌套其他的 Joint PointSpring 允许你用通知的地方,方法有关的前前后后(包括抛出异常)
切入点(Pointcut)对连接点进行拦截的定义指定通知到哪个方法,说明在哪干
切面(Aspect)切面类的定义,里面包含了切入点(Pointcut)和通知(Advice)的定义切面就是通知和切入点的结合
目标对象(Target Object)切入点选择的对象,也就是需要被通知的对象;由于 Spring AOP 通过代理模式实现,所以该对象永远是被代理对象业务逻辑本身
织入(Weaving)把切面应用到目标对象从而创建出 AOP 代理对象的过程。织入可以在编译期、类装载期、运行期进行,而 Spring 采用在运行期完成切点定义了哪些连接点会得到通知
引入(Introduction )可以在运行期为类动态添加方法和字段,Spring 允许引入新的接口到所有目标对象引入就是在一个接口/类的基础上引入新的接口增强功能
AOP 代理(AOP Proxy )Spring AOP 可以使用 JDK 动态代理或者 CGLIB 代理,前者基于接口,后者基于类通过代理来对目标对象应用切面

(二)Spring AOP

1. 简述

AOP 是 Spring 框架中的一个核心内容。在 Spring 中,AOP 代理可以用 JDK 动态代理或者 CGLIB 代理 CglibAopProxy 实现。Spring 中 AOP 代理由 Spring 的 IOC 容器负责生成和管理,其依赖关系也由 IOC 容器负责管理。

2. 相关注解

注解说明
@Aspect将一个 java 类定义为切面类
@Pointcut定义一个切入点,可以是一个规则表达式,比如下例中某个 package 下的所有函数,
也可以是一个注解等
@Before在切入点开始处切入内容
@After在切入点结尾处切入内容
@AfterReturning在切入点 return 内容之后处理逻辑
@Around在切入点前后切入内容,并自己控制何时执行切入点自身的内容
@AfterThrowing用来处理当切入内容部分抛出异常之后的处理逻辑
@Order(100)AOP 切面执行顺序, @Before 数值越小越先执行,@After 和 @AfterReturning 数值越大越先执行

其中 @Before、@After、@AfterReturning、@Around、@AfterThrowing 都属于通知(Advice)。

3. @Pointcut 切入点的不同表达式 示例

当然,以下是一些不同类型的切入点表达式(Pointcut Expressions)的示例,这些示例通常用于AOP(面向切面编程)框架中,如Spring AOP。

1. execution 表达式

这是最常用的切入点表达式,用于匹配方法执行的连接点。

// 匹配com.example.service包及其子包中所有类的所有方法
execution(* com.example.service..*.*(..))

// 匹配com.example.service.UserService类中所有的public方法
execution(public * com.example.service.UserService.*(..))

// 匹配所有返回类型为String,且方法名以find开头的public方法
execution(public String com.example..*.find*(..))

2. within 表达式

用于匹配连接点所在的Java类或包。

// 匹配com.example.service包及其子包中所有类的所有方法
within(com.example.service..*)

// 精确匹配com.example.service.UserService类中的所有方法
within(com.example.service.UserService)

注意:within表达式通常用于类型匹配,而不是方法签名匹配。

3. this 和 target 表达式

thistarget表达式用于匹配代理对象或目标对象。它们通常用于基于对象类型的过滤,而不是方法签名。

// 匹配代理对象实现了MyInterface接口的所有连接点
this(com.example.MyInterface)

// 匹配目标对象实现了MyInterface接口的所有连接点
target(com.example.MyInterface)

注意:这些表达式在Spring AOP中可能不直接支持,因为Spring AOP是基于代理的,并且thistarget的区分在JDK动态代理和CGLIB代理中可能有所不同。但在AspectJ等更强大的AOP框架中,这些表达式是支持的。

4. args 表达式

args表达式用于匹配方法参数。

// 匹配所有第一个参数为String类型的方法
args(String, ..)

// 匹配所有参数中包含至少一个String类型的方法
args(.., String, ..)

// 精确匹配第一个参数为特定类型的方法
args(com.example.MyType, ..)

注意:args表达式中的参数类型是按顺序匹配的,但可以使用..来匹配任意数量的额外参数。

5. @annotation、@within、@target 和 @args 表达式

这些表达式基于注解来匹配连接点。

// 匹配所有被@Transactional注解标注的方法
@annotation(org.springframework.transaction.annotation.Transactional)

// 匹配所有在类级别被@Transactional注解标注的类中的方法
@within(org.springframework.transaction.annotation.Transactional)

// 匹配所有目标对象(不是代理对象)被@Service注解标注的类中的方法
@target(org.springframework.stereotype.Service)

// 匹配所有至少有一个参数被@Valid注解标注的方法
@args(javax.validation.Valid, ..)

这些表达式提供了强大的灵活性,允许开发者基于注解来定义切面的应用范围。

请注意,具体的语法和支持程度可能会根据你所使用的AOP框架(如Spring AOP、AspectJ等)而有所不同。上述示例主要基于Spring AOP和AspectJ的通用语法。

(三)开始使用

1. 引入AOP依赖

在Spring Boot中引入AOP就跟引入其他模块一样,非常简单,只需要在pom.xml中加入如下依赖:

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

2. 自定义注解

由于面向切面的切入点(Pointcut)支持多种写法,
我这边也用了注解形式的写法,因此就自定义了以下这个注解,供后面测试使用

package com.example.reactboot.aop;

import java.lang.annotation.*;

/**
 * 自定义注解类
 */
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档

public @interface  Aoplog {
    String value() default "";
}

3. 切面类

package com.example.reactboot.aop;


import com.google.gson.Gson;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;

/**
 * 系统日志:切面处理类
 *
 * @Aspect:声明该类为一个注解类;
 * @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为某个 package 下的方法,也可以是自定义注解等;
 * <p>
 * 切点定义好后,就是围绕这个切点做文章了:
 * @Before: 在切点之前,织入相关代码;
 * @After: 在切点之后,织入相关代码;
 * @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;
 * @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;
 * @Around: 在切入点前后织入代码,并且可以自由的控制何时执行切点;
 */
@Aspect
@Component
@Order(1)
public class WebLogAspect {

    private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    /**
     * execution 表达式
     * 可以基于方法的返回类型、包名、类名、方法名以及参数类型等信息来精确地匹配方法.
     * 以下这个例子:匹配了 com.example.reactboot 包及其子包中所有类的所有方法。
     */
    //@Pointcut("execution(* com.example.reactboot.*.*(..))")
    @Pointcut("execution(public * com.example.reactboot.controller..*.*(..))")
    public void webLog() {
    }

    /**
     * @annotation 表达式
     * 用于匹配被指定注解标注的方法。
     * 以下这个例子:匹配了所有被 com.example.reactboot.aop.Aoplog 注解标注的方法。
     */
    @Pointcut("@annotation(com.example.reactboot.aop.Aoplog)")
    public void aopLog() {
    }


    /**
     * 在切点之前织入
     * @param joinPoint
     * @throws Throwable
     *
     * 以下这个例子:使用了execution 的内容
     */
    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
        // 开始打印请求日志
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        // 打印请求相关参数
        logger.info("========== Start ==========");
        // 打印请求 url
        logger.info("请求URL: {}", request.getRequestURL().toString());
        // 打印 Http method
        logger.info("请求方法: {}", request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        logger.info("全路径以及执行方法 Class Method: {}.{}", joinPoint.getSignature().getDeclaringTypeName(), joinPoint.getSignature().getName());
        // 打印请求的 IP
        logger.info("请求IP: {}", request.getRemoteAddr());
        // 打印请求入参
        logger.info("入参Request Args: {}", new Gson().toJson(joinPoint.getArgs()));
    }

    /**
     * 在切点之后织入
     *
     * @throws Throwable
     */
    @After("webLog()")
    public void doAfter() throws Throwable {
        logger.info("========== End ==========");
        // 每个请求之间空一行
        logger.info("");
    }

    /**
     * 环绕
     *
     * @param proceedingJoinPoint
     * @return
     * @throws Throwable
     */
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();
        // 打印出参
        logger.info("出参Response Args  : {}", result);
        // 执行耗时
        logger.info("执行耗时Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
        return result;
    }
}

4. 测试一(切入点用execution 表达式)

以上的切面处理类,使用的 webLog 方法,用的为execution 表达式

控制层 代码

package com.example.reactboot.controller;

import com.example.reactboot.aop.Aoplog;
import com.example.reactboot.aop.WebLogAspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 单纯的@Controller,请求的资源面向的就是页面
 * 而 @RestController,请求的资源面向的是对象或者字符串
 */
@RestController
public class HelloController {

    private final static Logger logger = LoggerFactory.getLogger(HelloController.class);

    @RequestMapping("/index")
    public String sayHello(){
        logger.info("我是index接口");
        return "index";
    }
}

请求该接口后,控制台显示打印如下操作
是有切面日志输出的

在这里插入图片描述

5. 测试二(切入点用自定义注解方式)

先把以上切面类(WebLogAspect )里面的webLog()全部换成aopLog() 再进行测试

控制层 代码

我新写了一个 xiaoming 的接口,在它上面加上了@Aoplog注解

package com.example.reactboot.controller;

import com.example.reactboot.aop.Aoplog;
import com.example.reactboot.aop.WebLogAspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * 单纯的@Controller,请求的资源面向的就是页面
 * 而 @RestController,请求的资源面向的是对象或者字符串
 */
@RestController
public class HelloController {

    private final static Logger logger = LoggerFactory.getLogger(HelloController.class);


    @RequestMapping("/index")
    public String sayHello(){
        logger.info("我是index接口");
        return "index";
    }

    @Aoplog(value = "xiaoming")
    @RequestMapping("/xiaoming")
    public String xiaoming(){
        logger.info("我是xiaoming接口");
        return "xiaoming";
    }
}

两个接口都请求后,控制台显示打印如下操作
只有加了注解的才会有切面日志输出

在这里插入图片描述

参考文章
【1】Spring Boot AOP 切面统一打印请求与响应日志
【2】Spring Boot 2.X(八):Spring AOP 实现简单的日志切面
【3】在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
【4】springboot项目使用切面记录用户操作日志
【5】Spring Boot中使用AOP统一处理Web请求日志

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

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

相关文章

【芯智雲城】UDStore定制化存储模组和技术解决方案

一、方案详情&#xff1a; UDStore芯宇存储专注行业应用&#xff0c;根据不同应用场景&#xff0c;为客户提供包括车规级、工业级、工规宽温及高耐久型的存储模组产品和技术解决方案&#xff0c;可提供的产品和解决方案类型包括如下&#xff1a; 二、关键技术&#xff1a; 1&…

WLAN DNS proxy settings (Win 10)

WLAN DNS proxy settings (Win 10) 114.114.114.114 8.8.8.8

Ubuntu 22.04 安装 MySQL 8

Ubuntu 22.04 安装 MySQL 8 本文描述了Ubuntu安装MySQL 8的方法 CentOS7 的安装方法点击此处跳转 Windows 的安装方法点击此处跳转 Docker 的安装方法点击此处跳转 正文开始&#xff1a; 在一切开始之前&#xff0c;建议先切换到root #输入下方名&#xff0c;然后输入当…

【JavaSec】Java反射知识点补充

0x03反射-补充零散知识点 文章目录 0x03反射-补充零散知识点Runtime类setAccessible(true)三种命令执行的方法static变量赋值 前面学过 就不多说final变量赋值InDirect final间接赋值static final 向大佬致敬&#xff1a; https://drun1baby.top Runtime类 Runtime 类中有 …

JAVA itextpdf 段落自动分页指定固定行距打印

JAVA itextpdf 段落自动分页指定固定行距打印 前言&#xff1a;公司有个需求&#xff0c;打印的合同模板左上角要加上logo的图标。但是itext pdf 自动分页会按照默认的顶部高分页打印内容的&#xff0c;导致从第二页开始logo图标就会把合同的内容给覆盖掉了。然后尝试了挺多方法…

Electron31-ViteAdmin桌面端后台|vite5.x+electron31+element-plus管理系统Exe

原创自研Vue3Electron31ElementPlus桌面端轻量级后台管理Exe系统。 基于最新前端技术栈Vite5.x、Vue3、Electron31、ElementPlus、Vue-I18n、Echarts实战开发桌面端高颜值后台管理模板。内置4种布局模板&#xff0c;支持i18n国际化、动态权限路由&#xff0c;实现了表格、表单、…

基于Spring Boot的大学校园生活信息平台的设计与实现pf

TOC springboot523基于Spring Boot的大学校园生活信息平台的设计与实现pf 绪论 1.1 研究背景 当前社会各行业领域竞争压力非常大&#xff0c;随着当前时代的信息化&#xff0c;科学化发展&#xff0c;让社会各行业领域都争相使用新的信息技术&#xff0c;对行业内的各种相关…

记录阿里云服务器购买和域名绑定解析的流程

购买阿里云域名和服务器 因为App备案的原因&#xff0c;需要购买域名和服务器&#xff0c;这篇文章介绍在阿里云上购买相关服务的流程。 注册阿里云的流程比较简单这里不再赘述了。请参考我之前写的 阿里云账号注册详细教程 购买顺序&#xff1a;一般是先购买阿里云服务器&…

【乐吾乐大屏可视化组态编辑器】事件交互-场景交互

场景交互 在线使用&#xff1a;https://v.le5le.com/ 乐吾乐大屏可视化可以实现大屏页面与内嵌2d/3d场景相互通信&#xff0c;底层原理是利用了iframe通过postMessage发送消息。 下面以2d场景为例&#xff0c;实现步骤如下&#xff1a; 1. 首先配置场景2&#xff08;被嵌入…

Postman接口测试项目实战

第 1 章 什么是接口测试 1.1、为什么要进行接口测试 目前除了特别Low的公司外&#xff0c;开发都是前后端分离的&#xff0c;就是说前端有前端的工程师进行编码&#xff0c;后端有后端的工程师进行编码&#xff0c;前后端进行数据基本都是通过接口进行交互的。 1.2、接口测…

ant design 的 tree 如何作为角色中的权限选择之一

这种功能如何弄呢&#xff1f; 编辑的时候要让权限能选中哦。 <ProForm.Item name"permissions" label{intl.formatMessage({ id: permission_choose })}><Spin spinning{loading}><TreecheckableonExpand{onExpand}expandedKeys{expandedKeys}auto…

StringJoiner以及字符串小练习

概述 String Joiner跟String Builder一样&#xff0c;亦可以看成是一个容器&#xff0c;创建之后iu里面的内容是可变的 作用 提高字符串的操作效率&#xff0c;而且代码编写特别简洁&#xff0c;但是目前市场上很少有人用 JDK8 package stringdemo;import java.util.String…

iTOP-3A5000开发板流畅运行国产系统外加机箱就是一台电脑主机

性能强 采用全国产龙芯3A5000处理器&#xff0c;基于龙芯自主指令系统 (LoongArch)的LA464微结构&#xff0c;并进一步提升频率&#xff0c;降低功耗&#xff0c;优化性能。 桥片 采用龙芯 7A2000&#xff0c;支持PCIE 3.0、USB 3.0和 SATA 3.0.显示接口2 路、HDMI 和1路 VGA&a…

插入数据时,出现存在重复数据异常,捕获异常的信息

Cause: java.sql.SQLIntegrityConstraintViolationException: Duplicate entry xiaomi111-啊啊啊啊 for key edu_class.institution_account 执行插入操作的时候抛出这个异常 怎么捕获这个异常 可以判断 e instanceof 某个具体的异常 然后再进像下面操作&#xff0c;打印出来…

重生奇迹MU:‌重塑经典,‌再创辉煌

在浩瀚的游戏宇宙中&#xff0c;‌有一颗璀璨的星辰&#xff0c;‌它承载着无数玩家的青春回忆与梦想&#xff0c;‌那就是——重生奇迹MU‌。‌作为一款历经岁月洗礼的经典网游&#xff0c;重生奇迹MU不仅见证了游戏行业的蓬勃发展&#xff0c;‌更以其独特的魅力&#xff0c;…

倒计时:可添加可删除的倒计时函数

<!DOCTYPE html> <html lang"zh-CN"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>倒计时</title> </head><body>…

关于医疗器械维修行业的一些思考

在当今医疗体系中&#xff0c;医疗器械维修行业扮演着不可或缺的角色。作为一名长期关注这一领域的人士&#xff0c;我对其有着一些个人的看法。 首先&#xff0c;医疗器械维修行业的重要性不言而喻。先进的医疗器械是现代医疗诊断和治疗的重要工具&#xff0c;而确保这些设备…

计算机组成原理(3):存储系统

1 存储器概述 主存储器其实就是内存&#xff01; 1.1 存储器的层次结构 ​ 存储器的三大评判指标&#xff1a;速度、容量、价格 ​ 使用任意一种存储器&#xff0c;都无法满足用户对存储器 高速、大容量、低价格 的需求&#xff0c;所以采用 多级结构 形成对应的 “存储体系“。…

从零开始学习网络安全渗透测试之信息收集篇——(二)WEB前端JS架构框架识别泄漏提取API接口枚举FUZZ爬虫插件项目

0、什么是JS渗透测试? 在Javascript中也存在变量和函数&#xff0c;当存在可控变量及函数调用即可参数漏洞JS开发的WEB应用和PHP&#xff0c;JAVA,NET等区别在于即没有源代码&#xff0c;也可以通过浏览器的查看源代码获取真实的点。获取URL&#xff0c;获取JS敏感信息&#…

mkv怎么改成mp4?3种mkv转mp4格式方法的介绍

mkv怎么改成mp4&#xff1f;将MKV格式视频转换为MP4格式&#xff0c;能显著提升兼容性&#xff0c;让视频在更多设备、平台上流畅播放。无论是智能手机、平板电脑、智能电视还是网页浏览器&#xff0c;MP4格式都具备广泛的支持&#xff0c;从而扩大视频的传播范围和受众群体。这…