SpringBoot使用AOP记录接口操作日志的方法

目录
  • 一、操作日志简介
    • 1.1、系统日志和操作日志的区别
    • 1.2、操作日志记录实现方式
  • 二、AOP面向切面编程
    • 2.1、AOP简介
    • 2.2、AOP作用
    • 2.3、AOP相关术语
    • 2.4、JointPoint和ProceedingJoinPoint
    • 2.5、AOP相关注解
  • 三、AOP切面实现接口日志记录
    • 3.1、引入AOP依赖
    • 3.2、创建日志信息封装类WebLog
    • 3.3、创建切面类WebLogAspect
    • 3.4、调用接口进行测试
  • 四、AOP切面+自定义注解实现接口日志记录
    • 4.1、自定义日志注解
    • 4.2、定义拦截操作日志的切面
    • 4.3、在接口上增加操作日志注解
    • 4.4、调用接口,可以看到输出的日志信息

**前言:**我们项目中可能有这种需求,每个人请求了哪些接口?做了什么事情?参数是什么?重要的接口我们需要记录操作日志以便查找。操作日志和系统日志不一样,操作日志必须要做到简单易懂。所以如何让操作日志不跟业务逻辑耦合,如何让操作日志的内容易于理解,如何让操作日志的接入更加简单?我们不可能在每个接口中去一一处理,可以借助Spring提供的AOP能力+自定义注解轻松应对。

一、操作日志简介

日志记录量是很大的,所以只记录关键地方并按期归档,最好是存在如elasticsearch中;如果存在数据库中,分表是不错的选择。

1.1、系统日志和操作日志的区别

系统日志:系统日志主要是为开发排查问题提供依据,一般打印在日志文件中;系统日志的可读性要求没那么高,日志中会包含代码的信息,比如在某个类的某一行打印了一个日志。

操作日志:主要是对某个对象进行新增操作或者修改操作后记录下这个新增或者修改,操作日志要求可读性比较强,因为它主要是给用户看的,比如订单的物流信息,用户需要知道在什么时间发生了什么事情。再比如,客服对工单的处理记录信息。

操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。

比如LogUtil.log(orderNo,“订单创建”,“小明”) :这里解释下为什么记录操作日志的时候都绑定了一个 OrderNo,因为操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。当查询业务的操作日志的时候,会查询针对这个订单的的所有操作,所以代码中加上了 OrderNo,记录操作日志的时候需要记录下操作人,所以传了操作人“小明”进来。

操作日志的记录格式大概分为下面几种:

  • 单纯的文字记录,比如:2021-09-16 10:00 订单创建。
  • 简单的动态的文本记录,比如:2021-09-16 10:00 订单创建,订单号:NO.11089999,其中涉及变量订单号“NO.11089999”。
  • 修改类型的文本,包含修改前和修改后的值,比如:2021-09-16 10:00 用户小明修改了订单的配送地址:从“金灿灿小区”修改到“银盏盏小区” ,其中涉及变量配送的原地址“金灿灿小区”和新地址“银盏盏小区”。
  • 修改表单,一次会修改多个字段。

1.2、操作日志记录实现方式

(1)使用 Canal 监听数据库记录操作日志

Canal是阿里开源的一款基于 MySQL 数据库增量日志解析中间件,提供增量数据订阅和消费的开源组件,通过采用监听数据库 Binlog 的方式,这样可以从底层知道是哪些数据做了修改,然后根据更改的数据记录操作日志。

这种方式的优点是和业务逻辑完全分离。缺点也很明显,局限性太高,只能针对数据库的更改做操作日志记录,如果修改涉及到其他团队的 RPC 的调用,就没办法监听数据库了。举个例子:给用户发送通知,通知服务一般都是公司内部的公共组件,这时候只能在调用 RPC 的时候手工记录发送通知的操作日志了。

(2)高度代码耦合:在业务逻辑中直接调用日志记录接口

通过日志文件的方式记录,这样就可以把日志单独保存在一个文件中,然后通过日志收集可以把日志保存在 Elasticsearch或者数据库中,接下来我们看下如何生成可读的操作日志。

//操作日志记录的是:某一个“时间”“谁”对“什么”做了什么“事情”。
log.info("订单创建")
log.info("订单已经创建,订单编号:{}",?orderNo)
log.info("修改了订单的配送地址:从“{}”修改到“{}”,?"金灿灿小区",?"银盏盏小区")

但是当业务变得复杂后,记录操作日志放在业务代码中会导致业务的逻辑比较繁杂。

(3)采用AOP方式:AOP方式能和业务逻辑解耦

为了解决上面问题,一般采用 AOP 的方式记录日志,让操作日志和业务逻辑解耦,接下来看一个简单的 AOP 日志的例子。

@LogRecord(content="修改了配送地址")
public void modifyAddress(updateDeliveryRequest request){
//?更新派送信息?电话,收件人、地址
doUpdate(request);
}

我们可以在注解的操作日志上记录固定文案,这样业务逻辑和业务代码可以做到解耦,让我们的业务代码变得纯净起来。可能有同学注意到,上面的方式虽然解耦了操作日志的代码,但是记录的文案并不符合我们的预期,文案是静态的,没有包含动态的文案,因为我们需要记录的操作日志是:用户%s修改了订单的配送地址,从“%s”修改到“%s”。接下来,我们介绍一下如何优雅地使用 AOP 生成动态的操作日志。

二、AOP面向切面编程

2.1、AOP简介

AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。**这种在运行时,动态地将代码切入到类的指定方法或指定位置上的编程思想就是面向切面的编程。**利用AOP可以将日志记录,性能统计,安全控制,事务处理,异常处理等代码从业务逻辑代码中划分出来作为公共部分,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

2.2、AOP作用

日志记录,性能统计,安全控制,事务处理,异常处理等等。

在面向切面编程AOP的思想里面,核心业务和切面通用功能(例如事务处理、日志管理、权限控制等)分别独立进行开发,然后把切面功能和核心业务功能 “编织” 在一起,这就叫AOP。这种思想有利于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。

2.3、AOP相关术语

通知(Advice)

通知描述了切面要完成的工作以及何时执行。比如我们的日志切面需要记录每个接口调用时长,就需要在接口调用前后分别记录当前时间,再取差值。

  • 前置通知(Before):在目标方法调用前调用通知功能;
  • 后置通知(After):在目标方法调用之后调用通知功能,不关心方法的返回结果;
  • 返回通知(AfterReturning):在目标方法成功执行之后调用通知功能;
  • 异常通知(AfterThrowing):在目标方法抛出异常后调用通知功能;
  • 环绕通知(Around):通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为。

切点(Pointcut)

切点定义了通知功能被应用的范围。比如日志切面的应用范围就是所有接口,即所有controller层的接口方法。

切面(Aspect)

切面是通知和切点的结合,定义了何时、何地应用通知功能。

引入(Introduction)

在无需修改现有类的情况下,向现有的类添加新方法或属性。

织入(Weaving)

把切面应用到目标对象并创建新的代理对象的过程。

连接点(JoinPoint)

通知功能被应用的时机。比如接口方法被调用的时候就是日志切面的连接点。

2.4、JointPoint和ProceedingJoinPoint

JointPoint是程序运行过程中可识别的连接点,这个点可以用来作为AOP切入点。JointPoint对象则包含了和切入相关的很多信息,比如切入点的方法,参数、注解、对象和属性等。我们可以通过反射的方式获取这些点的状态和信息,用于追踪tracing和记录logging应用信息。

(1)JointPoint
通过JpointPoint对象可以获取到下面信息

# 返回目标对象,即被代理的对象
Object getTarget();

# 返回切入点的参数
Object[] getArgs();

# 返回切入点的Signature
Signature getSignature();

# 返回切入的类型,比如method-call,field-get等等,感觉不重要?
String getKind();

(2)ProceedingJoinPoint

Proceedingjoinpoint 继承了 JoinPoint。是在JoinPoint的基础上暴露出 proceed 这个方法。proceed很重要,这个是aop代理链执行的方法。环绕通知=前置+目标方法执行+后置通知,proceed方法就是用于启动目标方法执行的。

暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到JoinPoint,,这也是环绕通知和前置、后置通知方法的一个最大区别。这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。

ProceedingJoinPoint可以获取切入点的信息:

  • 切入点的方法名字及其参数
  • 切入点方法标注的注解对象(通过该对象可以获取注解信息)
  • 切入点目标对象(可以通过反射获取对象的类名,属性和方法名)
//获取切入点方法的名字,getSignature());是获取到这样的信息 :修饰符+ 包名+组件名(类名) +方法名
String methodName = joinPoint.getSignature().getName()

//获取方法的参数,这里返回的是切入点方法的参数列表
Object[] args = joinPoint.getArgs();

//获取方法上的注解
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null)
{
xxxxxx annoObj= method.getAnnotation(xxxxxx.class);
}

//获取切入点所在目标对象
Object targetObj =joinPoint.getTarget();
//可以发挥反射的功能获取关于类的任何信息,例如获取类名如下
String className = joinPoint.getTarget().getClass().getName();

2.5、AOP相关注解

Spring中使用注解创建切面

  • @Aspect:用于定义切面
  • @Before:通知方法会在目标方法调用之前执行
  • @After:通知方法会在目标方法返回或抛出异常后执行
  • @AfterReturning:通知方法会在目标方法返回后执行
  • @AfterThrowing:通知方法会在目标方法抛出异常后执行
  • @Around:通知方法会将目标方法封装起来
  • @Pointcut:定义切点表达式

切点表达式:指定了通知被应用的范围,表达式格式:

execution(方法修饰符 返回类型 方法所属的包.类名.方法名称(方法参数)
//com.hs.demo.controller包中所有类的public方法都应用切面里的通知
execution(public * com.hs.demo.controller..(…))
//com.hs.demo.service包及其子包下所有类中的所有方法都应用切面里的通知
execution(* com.hs.demo.service….(…))
//com.hs.demo.service.EmployeeService类中的所有方法都应用切面里的通知
execution(* com.hs.demo.service.EmployeeService.*(…))

(1)@POINTCUT定义切入点,有以下2种方式:

方式一:设置为注解@LogFilter标记的方法,有标记注解的方法触发该AOP,没有标记就没有。

@Aspect
@Component
public class LogFilter1Aspect {
	@Pointcut(value = "@annotation(com.hs.aop.annotation.LogFilter)")
	public void pointCut(){

	}
}

自定义注解LogFilter:

@Target(ElementType.METHOD)
@Retention(value = RetentionPolicy.RUNTIME)
public @interface LogFilter1 {
}

对应的Controller方法如下,手动添加@LogFilter注解:

@RestController
public class AopController {
	@RequestMapping("/aop")
	@LogFilter
	public String aop(){
	    System.out.println("这是执行方法");
	    return "success";
	}
}

方式二:采用表达式批量添加切入点,如下方法,表示AopController下的所有public方法都添加LogFilter1切面。

@Pointcut(value = "execution(public * com.train.aop.controller.AopController.*(..))")
public void pointCut(){

}

(2)@Around环绕通知

@Around集成了@Before、@AfterReturing、@AfterThrowing、@After四大通知。需要注意的是,他和其他四大通知注解最大的不同是需要手动进行接口内方法的反射后才能执行接口中的方法,换言之,@Around其实就是一个动态代理。

   /**
	 * 环绕通知是spring框架为我们提供的一种可以在代码中手动控制增强部分什么时候执行的方式。
	 *
	 */
	public void aroundPringLog(ProceedingJoinPoint pjp)
   {
      //拿到目标方法的方法签名
       Signature signature = pjp.getSignature();
      //获取方法名
      String name = signature.getName();

	  try {
			//@Before
			System.out.println("【环绕前置通知】【"+name+"方法开始】");
            //这句相当于method.invoke(obj,args),通过反射来执行接口中的方法
			proceed = pjp.proceed();
			//@AfterReturning
			System.out.println("【环绕返回通知】【"+name+"方法返回,返回值:"+proceed+"】");
		} catch (Exception e) {
			//@AfterThrowing
			System.out.println("【环绕异常通知】【"+name+"方法异常,异常信息:"+e+"】");
		}
        finally{
			//@After
			System.out.println("【环绕后置通知】【"+name+"方法结束】");
		}
	}

proceed = pjp.proceed(args)这条语句其实就是method.invoke,以前手写版的动态代理,也是method.invoke执行了,jdk才会利用反射进行动态代理的操作,在Spring的环绕通知里面,只有这条语句执行了,spring才会去切入到目标方法中。

为什么说环绕通知就是一个动态代理呢?

proceed = pjp.proceed(args)这条语句就是动态代理的开始,当我们把这条语句用try-catch包围起来的时候,在这条语句前面写的信息,就相当于前置通知,在它后面写的就相当于返回通知,在catch里面写的就相当于异常通知,在finally里写的就相当于后置通知。

三、AOP切面实现接口日志记录

通过AOP面向切面编程技术,实现操作日志记录功能以便进行信息监控和信息统计,不侵入业务代码逻辑,提高代码的可重用性。

3.1、引入AOP依赖

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

3.2、创建日志信息封装类WebLog

用于封装需要记录的日志信息,包括操作的描述、时间、消耗时间、url、请求参数和返回结果等信息。

@Data
public class WebLog {
    /**
     * 操作描述
     */
    private String description;

    /**
     * 操作用户
     */
    private String username;

    /**
     * 操作时间
     */
    private Long startTime;

    /**
     * 消耗时间
     */
    private Integer spendTime;

    /**
     * 根路径
     */
    private String basePath;

    /**
     * URI
     */
    private String uri;

    /**
     * URL
     */
    private String url;

    /**
     * 请求类型
     */
    private String method;

    /**
     * IP地址
     */
    private String ip;

    /**
     * 请求参数
     */
    private Object parameter;

    /**
     * 请求返回的结果
     */
    private Object result;

}

3.3、创建切面类WebLogAspect

import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 统一日志处理切面
 */
@Aspect
@Component
@Order(1)
@Slf4j
public class WebLogAspect {

    //定义切点表达式,指定通知功能被应用的范围
    @Pointcut("execution(public * com.hs.notice.controller.*.*(..))")
    public void webLog() {
    }

    @Before("webLog()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {
    }

    /**value切入点位置
     * returning 自定义的变量,标识目标方法的返回值,自定义变量名必须和通知方法的形参一样
     * 特点:在目标方法之后执行的,能够获取到目标方法的返回值,可以根据这个返回值做不同的处理
     */
    @AfterReturning(value = "webLog()", returning = "ret")
    public void doAfterReturning(Object ret) throws Throwable {
    }

    //通知包裹了目标方法,在目标方法调用之前和之后执行自定义的行为
    //ProceedingJoinPoint切入点可以获取切入点方法上的名字、参数、注解和对象
    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable
    {
        long startTime = System.currentTimeMillis();
        //获取当前请求对象
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        //记录请求信息
        WebLog webLog = new WebLog();

        //前面是前置通知,后面是后置通知
        Object result = joinPoint.proceed();

        Signature signature = joinPoint.getSignature();
        MethodSignature methodSignature = (MethodSignature) signature;
        Method method = methodSignature.getMethod();
        long endTime = System.currentTimeMillis();
        String urlStr = request.getRequestURL().toString();
        webLog.setBasePath(StrUtil.removeSuffix(urlStr, URLUtil.url(urlStr).getPath()));
        webLog.setIp(request.getRemoteUser());
        webLog.setMethod(request.getMethod());
        webLog.setParameter(getParameter(method, joinPoint.getArgs()));
        webLog.setResult(result);
        webLog.setSpendTime((int) (endTime - startTime));
        webLog.setStartTime(startTime);
        webLog.setUri(request.getRequestURI());
        webLog.setUrl(request.getRequestURL().toString());
        log.info("{}", JSONUtil.parse(webLog));
        return result;
    }

    /**
     * 根据方法和传入的参数获取请求参数
     */
    private Object getParameter(Method method, Object[] args)
   {
        List<Object> argList = new ArrayList<>();
        Parameter[] parameters = method.getParameters();
        for (int i = 0; i < parameters.length; i++) {
            //将RequestBody注解修饰的参数作为请求参数
            RequestBody requestBody = parameters[i].getAnnotation(RequestBody.class);
            if (requestBody != null) {
                argList.add(args[i]);
            }
            //将RequestParam注解修饰的参数作为请求参数
            RequestParam requestParam = parameters[i].getAnnotation(RequestParam.class);
            if (requestParam != null) {
                Map<String, Object> map = new HashMap<>();
                String key = parameters[i].getName();
                if (!StringUtils.isEmpty(requestParam.value())) {
                    key = requestParam.value();
                }
                map.put(key, args[i]);
                argList.add(map);
            }
        }
        if (argList.size() == 0) {
            return null;
        } else if (argList.size() == 1) {
            return argList.get(0);
        } else {
            return argList;
        }
    }
}

3.4、调用接口进行测试

随便访问一个com.hs.notice.controller包下的接口,可以看到WebLogAspect输出的日志信息:

2022-01-07 10:43:33.732  INFO 11400 --- [nio-8086-exec-7] com.cernet.notice.util.WebLogAspect: 
{"result":{"code":200,"data":{"appName":"Tiktok","isSend":1,"id":1,"sendAT":1640596730378,
"tilte":"【测试数据】","receiverEmail":"110@qq.com","content":"测试"},"message":"请求成功"},
"basePath":"http://localhost:8086","method":"GET","startTime":1641523413722,
"uri":"/api/email-notices/1","url":"http://localhost:8086/api/email-notices/1","spendTime":6}

四、AOP切面+自定义注解实现接口日志记录

4.1、自定义日志注解

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

/**
?* 定义操作日志注解
?*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface OperatorLog
{
 // 操作
 String operate();

 // 模块
 String module();
}

4.2、定义拦截操作日志的切面

import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;
import com.elon.springbootdemo.manager.OperatorLog;
import com.elon.springbootdemo.model.ResponseModel;

/**
 * 操作日志切面定义
 */
@Aspect
@Component
public class OperatorLogApsect {

    private static Logger logger = LogManager.getLogger(OperatorLogApsect.class);

    @Pointcut("@annotation(com.hs.springbootdemo.aop.OperatorLog)")
    public void operatorLog() {

    }

    @SuppressWarnings("rawtypes")
    @AfterReturning(returning="result", pointcut="operatorLog()&&@annotation(log)")
    public void afterReturn(JoinPoint joinPoint, ResponseModel result, OperatorLog log) {

        /**
         * 接口调用信息可以记日志,也可以写到数据库
         */
        StringBuilder sb = new StringBuilder();
        sb.append("模块:").append(log.module());
        sb.append("|操作:").append(log.operate());
        sb.append("|接口名称:").append(joinPoint.getSignature().getName());
        sb.append("|错误码:").append(result.getRetCode());
        sb.append("|错误信息:").append(result.getErrorMsg());
        logger.info(sb.toString());
    }
}

4.3、在接口上增加操作日志注解

@RequestMapping(value="/v1/query-user", method=RequestMethod.GET)
@OperatorLog(operate="查询用户", module="用户管理")
public ResponseModel queryUser(@RequestParam(value="user", required=true) String user)?
{
log.info("[INFO]user info:" + user);
log.warn("[WARN]user info:" + user);
log.error("[ERROR]user info:" + user);
return ResponseModel.success(null);
}

4.4、调用接口,可以看到输出的日志信息

参考链接:

美团技术团队—如何优雅地记录操作日志?

SpringAOP中的ProceedingJoinPoint使用,配合注解的方式

使用(Annotation)自定义注解做日志记录

Spring 过滤器 拦截器 AOP区别

SpringBoot 拦截器解析

使用拦截器(intercept)和AOP写操作日志-springboot

到此这篇关于SpringBoot使用AOP记录接口操作日志的文章就介绍到这了,更多相关SpringBoot AOP记录接口内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • 使用SpringBoot AOP 记录操作日志、异常日志的过程

    平时我们在做项目时经常需要对一些重要功能操作记录日志,方便以后跟踪是谁在操作此功能:我们在操作某些功能时也有可能会发生异常,但是每次发生异常要定位原因我们都要到服务器去查询日志才能找到,而且也不能对发生的异常进行统计,从而改进我们的项目,要是能做个功能专门来记录操作日志和异常日志那就好了, 当然我们肯定有方法来做这件事情,而且也不会很难,我们可以在需要的方法中增加记录日志的代码,和在每个方法中增加记录异常的代码,最终把记录的日志存到数据库中.听起来好像很容易,但是我们做起来会发现,做这项工作很繁

  • Springboot接口项目如何使用AOP记录日志

    一. 背景 一直想给项目构建一个统一的日志收集系统,先迈出第一步,构建一个日志收集类,用AOP实现无侵入日志收集 二. 环境 1.此随笔内容基于spring boot项目 2.数据库为mysql 5.7.9版本 3.jdk 版本为1.8 三. 说明 此版采用数据库存储,之后考虑使用elasticsearch等工具存储 四. 内容 1.构建日志采集实体类:BaseLogMessage public class BaseLogMessage { private String serverIP; pr

  • 详解基于SpringBoot使用AOP技术实现操作日志管理

    操作日志对于程序员或管理员而言,可以快速定位到系统中相关的操作,而对于操作日志的管理的实现不能对正常业务实现进行影响,否则即不满足单一原则,也会导致后续代码维护困难,因此我们考虑使用AOP切面技术来实现对日志管理的实现. 文章大致内容: 1.基本概念 2.基本应用 3.日志管理实战 对这几部分理解了,会对AOP的应用应该很轻松. 一.基本概念 项目 描述 Aspect(切面) 跨越多个类的关注点的模块化,切面是通知和切点的结合.通知和切点共同定义了切面的全部内容--它是什么,在何时和何处完成其功

  • SpringBoot中使用AOP打印接口日志的方法

    前言 AOP 是 Aspect Oriented Program (面向切面)的编程的缩写.他是和面向对象编程相对的一个概念.在面向对象的编程中,我们倾向于采用封装.继承.多态等概念,将一个个的功能在对象中来实现.但是,我们在实际情况中也发现,会有另外一种需求就是一类功能在很多对象的很多方法中都有需要.例如有一些对数据库访问的方法有事务管理的需求,有很多方法中要求打印日志.按照面向对象的方式,那么这些相同的功能要在很多地方来实现或者在很多地方来调用.这就非常繁琐并且和这些和业务不相关的需求耦合太

  • SpringBoot使用AOP记录接口操作日志的方法

    目录 一.操作日志简介 1.1.系统日志和操作日志的区别 1.2.操作日志记录实现方式 二.AOP面向切面编程 2.1.AOP简介 2.2.AOP作用 2.3.AOP相关术语 2.4.JointPoint和ProceedingJoinPoint 2.5.AOP相关注解 三.AOP切面实现接口日志记录 3.1.引入AOP依赖 3.2.创建日志信息封装类WebLog 3.3.创建切面类WebLogAspect 3.4.调用接口进行测试 四.AOP切面+自定义注解实现接口日志记录 4.1.自定义日志注

  • SpringBoot使用AOP记录接口操作日志详解

    SpringBoot 使用 AOP 记录接口操作日志,供大家参考,具体内容如下 一.AOP简介 1.什么是AOP AOP:Aspect Oriented Programming 面向切面编程 AOP关注不是某一个类或某些方法:控制大量资源,关注的是大量的类和方法. 2.AOP应用场景以及常用术语 权限控制.缓存控制.事务控制.分布式追踪.异常处理等 Target:目标类,即需要被代理的类.例如:UserService Joinpoint(连接点):所谓连接点是指那些可能被拦截到的方法.例如:所有

  • 利用spring AOP记录用户操作日志的方法示例

    前言 最近项目已经开发完成,但发现需要加用户操作日志,如果返回去加也不太现实,所以使用springAOP来完成比较合适.下面来一起看看详细的介绍: 注解工具类: @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface LogAnnotation { String operateModelNm() default ""; String operateFuncNm() default

  • springboot利用aop实现接口异步(进度条)的全过程

    目录 一.前言 二.时序图 三.功能演示 四.关键代码 Controller AsyncAop AsyncService 五.源码地址 总结 一.前言 在项目中发现有接口(excel导入数据)处理数据需要耗时比较长的时间,是因为数据量比较大,同时数据的校验需要耗费一定时间,决定使用一种通用的方法解决这个问题. 解决方案:通过aop使接口异步处理,前端轮询另外一个接口查询进度. 目标: 1接口上一个注解即可实现接口异步(优化:可以通过header参数动态控制是否异步) 2一个方法实现进度条的更新

  • SpringBoot服务上实现接口限流的方法

    Sentinel是阿里巴巴开源的限流器熔断器,并且带有可视化操作界面. 在日常开发中,限流功能时常被使用,用于对某些接口进行限流熔断,譬如限制单位时间内接口访问次数:或者按照某种规则进行限流,如限制ip的单位时间访问次数等. 之前我们已经讲过接口限流的工具类ratelimter可以实现令牌桶的限流,很明显sentinel的功能更为全面和完善.来看一下sentinel的简介: https://github.com/spring-cloud-incubator/spring-cloud-alibab

  • Laravel-admin之修改操作日志的方法

    以前的后台都是使用自己手写的日志,但是最近公司工作较多,修改其他项目的任务就搭在了我的身上,今天用户要修改后台的操作日志界面,让登陆者可以更多地知道登录后端者的所有相关操作:包括谁修改了,谁删除了,谁审核了,谁添加了,这样才能根据相应的操作者进行相应的处罚. 我现在要修改的则是Laravel-admin后台,要想修改操作日志, 需要如下操作: 1.找到原始操作日志控制器文件LogController.php: \vendor\encore\laravel-admin\src\Controller

  • springboot+mybatis配置控制台打印sql日志的方法

    此处项目环境为简单的springboot+mybatis环境.可查看到上一篇文章搭建的简单springboot+mybatis的项目 想要控制台打印sql日志. 只需要在resources目录下添加logback.xml文件即可 logback内容如下.直接复制即可 <?xml version="1.0" encoding="UTF-8"?> <configuration debug="false"> <!--定义日

  • Spring Boot2集成AOPLog来记录接口访问日志

    前言 日志是一个Web项目中必不可少的部分,借助它我们可以做许多事情,比如问题排查.访问统计.监控告警等.一般通过引入slf4j的一些实现框架来做日志功能,如log4j,logback,log4j2,其性能也是依次增强.在springboot中,默认使用的框架是logback. 我们经常需要在方法开头或结尾加日志记录传入参数或返回结果,以此来复现当时的请求情况.但是手动添加日志,不仅繁琐重复,也影响代码的美观简洁.本文引入一个基于AOP实现的日志框架,并通过spring-boot-starter

  • 使用Spring AOP实现用户操作日志功能

    目录 我使用Spring AOP实现了用户操作日志功能 需求分析 功能实现 1. 需要一张记录日志的 Log 表 导出的 sql 如下: 2.我使用的是 Spring Boot 所以需要引入 spring aop 的 starter 3.Log 实体类 4.ILog 注解 5.切面类 LogAspect 总结 我使用Spring AOP实现了用户操作日志功能 今天答辩完了,复盘了一下系统,发现还是有一些东西值得拿出来和大家分享一下. 需求分析 系统需要对用户的操作进行记录,方便未来溯源 首先想到

随机推荐