springboot整合aop,实现日志操作

前言:

整合之前,我们要明白aop是什么,为什么要用aop,aop能帮我们做什么。

答:AOP是面向切面编程(Aspect-Oriented Programming)的简称,它是一种编程思想,旨在在面向对象编程(OOP)的基础上进行功能模块的解耦和隔离。在传统的业务处理代码中,通常需要进行事务处理日志记录等操作,这些操作会分散到各个方法中,增加了开发和维护的难度。AOP通过预编译方式和运行期动态代理实现,在不修改源代码的情况下,将分散在各个方法中的重复代码提取出来,然后在程序编译或运行时,再将这些提取出来的代码应用到需要执行的地方。

因此,AOP能够帮我们做以下事情:

  1. 降低业务逻辑的耦合性,提高程序的可重用型和开发效率。
  2. 对业务逻辑的各个部分进行隔离,便于模块化管理。
  3. 提取公共功能,减少重复代码,提高代码的可维护性和可读性。
  4. 提供一种新的编程视角和工具,使开发人员可以专注于业务逻辑的实现,而不用过多关注其他功能的实现。

AOP能够提高开发效率和代码质量,降低维护成本。

=========================================================================

一、AOP介绍

1、名词介绍

(1)切面(Aspect):切入点和通知的集合
(2)连接点(Joinpoint):目标对象中可以被增强的所有方法
(3)通知(Advice):增强的代码(逻辑),分为前置,后置,最终,异常,环绕
(4)切入点(Pointcut):目标对象中经过匹配最终增强的方法
(5)引入(Introduction):动态的为某个类增加和减少方法
(6)目标对象(Target Object):被代理的对象
(7)AOP代理对象(AOP Proxy):AOP框架创建的代理对象,用于实现切面,调用方法
(8)织入(Weaving):将通知应用到切入点的过程

2、注解介绍

(1)@EnableAspectJautoProxy 用于springboot启动类,代表开启注解aop功能支持
                proxyTargetClass 是否强制使用CGlib的动态代理,默认false
                exposeProxy 是否通过aop框架暴露该代理对象,aopContext能够访问
(2)@Aspect 用于标注切面类
(3)@Pointcut 用于标识切入点
                value 切入点表达式
(4) @Before 前置通知
(5)@AfterReturning 后置通知
(6)@AfterThrowing 异常通知
(7)@After 最终通知
(8)@Around 环绕通知,环绕通知代表了一个完整的流程,因此环绕通知和上面的四个通知任选其一使用

3、切入点表达式

(1)execution - 根据表达式匹配,使用最多

        execution([修饰符] 返回类型 [包名.类名].方法名(参数列表) [异常])

        支持的通配符有 *:匹配所有。..:匹配多级包或者多个参数。+表示类以及子类

(2)within - 匹配方法所在的包或者类

(3)this - 用于向通知方法中传入代理对象的引用

(4)target - 用于向通知方法中传入目标对象的引用

(5)args - 用于向通知方法中传入参数,并且匹配参数个数

(6)@args - 和args都是匹配参数,但是@args要求传入切入点的参数必须标注指定注解,且不能是SOURCE源码注解,比如Lombok的

(7)@within - 匹配加了某个注解的类中的所有方法

(8)@target - 与@within类似,但是要求标注到类上的注解,必须为RUNTIME的

(9)@annotation - 匹配加了某个注解的方法

(10)bean 通过spring容器中的beName匹配

        可以使用通配符*来标识以什么开头,以什么结尾

二、整合AOP实现,实现日志操作

1、引入依赖

<!-- springboot aop -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>2.0.32</version>
</dependency>

2、类

package com.mgx.demo.common.enums;

import lombok.AllArgsConstructor;
import lombok.Getter;


/**
 * @author mgx
 */
@AllArgsConstructor
@Getter
public enum CharacterEnum {

    /**
     * 特殊字符
     */

    //空白
    BLANK("")
    //空格
    , SPACE(" ")
    //换行
    , NEWLINE("\n")
    //enter换行
    , ENTER("\r")
    //左斜杠
    , SLASH("/")
    //双左斜杠
    , DOUBLE_SLASH("//")
    //反斜杠
    , BACKSLASH("\\")
    //单引号
    , QUOTES("'")
    //双引号
    , DOUBLE_QUOTES("\"")
    //撇号
    , APOSTROPHE("`")
    //艾特符
    , AT("@")
    //井号
    , HASHTAG("#")
    //dollar符
    , DOLLAR("$")
    //百分号
    , PERCENT("%")
    //异或运算符 数字相同返回0,否则为1
    , XOR("^")
    //and符
    , AND("&")
    //星号
    , ASTERISK("*")
    //等于号
    , EQUAL("=")
    //下划线
    , UNDERSCORE("_")
    //点
    , DOT(".")
    //句号
    , C_DOT("。")
    //逗号
    , COMMA(",")
    //中文逗号
    , C_COMMA(",")
    //管道符
    , PIPE("|")
    //双管道符
    , DOUBLE_PIPE("||")
    //问号
    , Q_MARK("?")
    //叹号
    , E_MARK("!")
    //加号
    , PLUS("+")
    //连字号、短横杠、减号
    , HYPHEN("-")
    //小于符
    , LT("<")
    //大于符
    , GT(">")
    //冒号
    , COLON(":")
    //分号
    , SEMICOLON(";")
    //中文分号
    , C_SEMICOLON(";")
    //左圆括号 round
    , L_R_BRACKETS("(")
    //右圆括号
    , R_R_BRACKETS(")")
    //左右圆括号
    , R_BRACKETS("()")
    //左方括号 square
    , L_S_BRACKETS("[")
    //右方括号
    , R_S_BRACKETS("]")
    //左右方括号
    , S_BRACKETS("[]")
    //左大括号 curly
    , L_C_BRACKETS("{")
    //右大括号
    , R_C_BRACKETS("{")
    //左右大括号
    , C_BRACKETS("{}")
    ;

    private final String character;

    public String value() {
        return character;
    }

}
package com.mgx.demo.utils;

import com.alibaba.fastjson.JSON;
import com.mgx.demo.common.enums.CharacterEnum;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.util.Arrays;
import java.util.Objects;

/**
 * @author mgx
 */
@Slf4j
public class LogUtil {

    /**
     * 接口请求日志
     *
     * @param param 接口获取参数
     */
    public static void param(Object... param) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        if (Objects.nonNull(sra)) {
            HttpServletRequest request = sra.getRequest();
            String url = request.getRequestURL().toString();
            log.info("===============++++请求++++================\n地址:{}\n参数:{}", url, Objects.isNull(param) ? CharacterEnum.BLANK.value() : Arrays.toString(param));
        }
    }

    /**
     * 接口请求日志
     *
     * @param param 封装后的参数,若数据结构较复杂,请考虑json转化string耗时及性能
     */
    public static void paramObject(Object param) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        if (Objects.nonNull(sra)) {
            HttpServletRequest request = sra.getRequest();
            String url = request.getRequestURL().toString();
            log.info("===============++++请求++++================\n地址:{}\n参数:{}", url, Objects.isNull(param) ? CharacterEnum.BLANK.value() : JSON.toJSONString(param));
        }
    }

    public static void logRequest(HttpServletRequest request) {
        log.info("===============++++请求++++================\n地址:{}\n方法:{}\nIP:{}", request.getRequestURL().toString(), request.getMethod(), request.getRemoteAddr());
    }

}
package com.mgx.demo.config.aop;

import com.alibaba.fastjson.JSON;

import com.mgx.demo.annotation.LogRequestParam;
import com.mgx.demo.utils.LogUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 *  自动打印日志
 */
@Aspect
@Component
public class WebLogAspect {
    private static final Logger logger = LoggerFactory.getLogger(WebLogAspect.class);

    @Pointcut("execution(public * com.mgx.demo.controller.*.*(..))")
    public void webLog() {}

    @AfterReturning(returning = "ret", pointcut = "webLog()")
    public void doAfterReturning(Object ret) {
        // 处理完请求,返回内容
        logger.info("RESPONSE : {}", JSON.toJSONString(ret));
    }

    @Before("@annotation(logRequestParam) || @within(logRequestParam)")
    public void doRequestParamLog(JoinPoint joinPoint, LogRequestParam logRequestParam) {
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        if (args != null) {
            if (args.length == 1) {
                LogUtil.paramObject(args[0]);
            } else {
                LogUtil.param(args);
            }
        }
    }

}
package com.mgx.demo.annotation;

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

/**
 * @author mgx
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface LogRequestParam {
}
<?xml version="1.0" encoding="UTF-8"?>
<configuration>

    <springProperty scope="context" name="logPath" source="project.log.config" defaultValue="${user.home}/springboot-mgx/logs"/>
    <property name="LOG_HOME" value="${logPath}"/>

    <!-- %m输出的信息, %p日志级别, %t线程名, %d日期, %c类的全名, %i索引 -->
    <!-- appender是configuration的子节点,是负责写日志的组件 -->
    <appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <!--<pattern>${CONSOLE_LOG_PATTERN}</pattern> -->
            <pattern>%date{yyyy-MM-dd HH:mm:ss} %highlight(%-5level) (%file:%line\)- %m%n</pattern>
            <!-- 控制台也要使用utf-8,不要使用gbk -->
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <!-- RollingFileAppender:滚动记录文件,先将日志记录到指定文件,当符合某个条件时,将日志记录到其他文件 -->
    <!-- 1.先按日期存日志,日期变了,将前一天的日志文件名重命名为xxx%日期%索引,新的日志仍然是sys.log -->
    <!-- 2.如果日期没有变化,但是当前日志文件的大小超过1kb时,对当前日志进行分割 重名名 -->
    <appender name="ALL" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <File>${LOG_HOME}/sys.log</File>
        <!-- rollingPolicy:当发生滚动时,决定 RollingFileAppender 的行为,涉及文件移动和重命名。 -->
        <!-- TimeBasedRollingPolicy: 最常用的滚动策略,它根据时间来制定滚动策略,既负责滚动也负责出发滚动 -->
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!-- 活动文件的名字会根据fileNamePattern的值,每隔一段时间改变一次 -->
            <!-- 文件名:pileLog/2020/10/10/sys.2020-10-10_13.log -->
            <fileNamePattern>${LOG_HOME}/%d{yyyy-MM/dd/HH}/sys.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <!-- maxFileSize:这是活动文件的大小,默认值是10MB -->
                <maxFileSize>30MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
        </rollingPolicy>
        <encoder>
            <!-- pattern节点,用来设置日志的输入格式 -->
            <pattern>%d %p (%file:%line\)- %m%n</pattern>
            <!-- 记录日志的编码 -->
            <charset>UTF-8</charset> <!-- 此处设置字符集 -->
        </encoder>
    </appender>

    <!--ERROR-->
    <appender name="ERROR" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>ERROR</level>
            <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY</onMismatch>
        </filter>
        <file>${LOG_HOME}/sys.error.log</file>
        <append>true</append>
        <rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/%d{yyyy-MM/dd/HH}/sys.error.%i.log</fileNamePattern>
            <maxFileSize>30MB</maxFileSize>
        </rollingPolicy>
        <encoder>
            <Pattern>%d %p (%file:%line\)- %m%n</Pattern>
            <charset>UTF-8</charset>
        </encoder>
    </appender>

    <contextListener class="ch.qos.logback.classic.jul.LevelChangePropagator">
        <resetJUL>true</resetJUL>
    </contextListener>

    <!-- 控制台日志输出级别 -->
    <root level="info">
        <appender-ref ref="CONSOLE"/>
        <appender-ref ref="ALL"/>
        <appender-ref ref="ERROR"/>
    </root>

    <!-- 指定项目中某个包,当有日志操作行为时的日志记录级别 -->
    <!-- com.dmyc为根包,也就是只要是发生在这个根包下面的所有日志操作行为的权限都是DEBUG -->
    <!-- 级别依次为【从高到低】:FATAL > ERROR > WARN > INFO > DEBUG > TRACE  -->
    <logger name="com.mgx.demo.controller" level="DEBUG"/>
    <logger name="com.mgx.demo.mapper" level="DEBUG"/>
    <logger name="springfox" level="ERROR"/>

</configuration>

application

 需要打印日志的地方,在类上加上自定义注解

3、测试

文章来源地址https://uudwc.com/A/aYg14

原文地址:https://blog.csdn.net/qq_42405688/article/details/132970648

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请联系站长进行投诉反馈,一经查实,立即删除!

上一篇 2023年09月24日 21:19
类和对象:运算符重载
下一篇 2023年09月24日 21:19