记录通过Aop实现 记录系统操作日志功能

通过Aop实现的方式比较简单:

步骤:

1.自定义一个注解,让它可用于 想要记录日志的方法上;
2.通过Aop 统一处理这些标记了自定义注解的类;
3.在Aop通知中添加逻辑,获取操作日志想要记录的信息,最后添加到自己设计的操作日志表里去;功能完成;

具体实现:

1.自定义一个注解,让它可用于 想要记录日志的方法上;

package com.lance.flowable.operationLog;

import java.lang.annotation.*;

/**
 * @description:自定义操作日志注解
 */
@Target(ElementType.METHOD)//描述注解使用范围,现在代表可以在方法上使用
@Retention(RetentionPolicy.RUNTIME)//标识注解的生命周期
@Documented //元注解,
public @interface Log {
//下面的属性可以自定义
    /**
     * 操作模块
     * @return
     */
    String modul() default "";

    /**
     * 操作类型
     * @return
     */
    String type() default "";

    /**
     * 操作说明
     * @return
     */
    String desc() default "";

}


2.通过Aop 统一处理这些标记了自定义注解的类;
3.在Aop通知中添加逻辑,获取操作日志想要记录的信息,最后添加到自己设计的操作日志表里去;功能完成;

package com.lance.flowable.operationLog;

import com.alibaba.fastjson.JSON;
import com.lance.flowable.utils.IPUtils;
import com.lance.flowable.utils.SecurityUserUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;

/**
 * @description:切面处理类,操作日志,异常日志,记录,处理
 */
@Aspect
@Component
public class LogAspect {

    /**
     * 操作版本号
     * 项目启动时从命令行传入,例如:java -jar xxx.war --version=201902
     */
    @Value("${version}")
    private String version;

    /**
     * 统计请求的处理时间
     */
    ThreadLocal<Long> startTime = new ThreadLocal<>();

    @Autowired
    private LogInfoService logInfoService;

    @Autowired
    private LogErrorInfoService logErrorInfoService;

    /**
     * @methodName:logPoinCut
     * @description:设置操作日志切入点 记录操作日志 在注解的位置切入代码
     * @author:tanyp
     * @dateTime:2021/11/18 14:22
     * @Params: []
     * @Return: void
     * @editNote:
     */
    @Pointcut("execution(* com.lance.flowable.controller..*.*(..))")
    public void logPoinCut() {
    }

    /**
     * @methodName:exceptionLogPoinCut
     * @description:设置操作异常切入点记录异常日志 扫描所有controller包下操作
     * @author:tanyp
     * @dateTime:2021/11/18 14:22
     * @Params: []
     * @Return: void
     * @editNote:
     */
    @Pointcut("execution(* com.lance.flowable.controller..*.*(..))")
    public void exceptionLogPoinCut() {

    }

    //前置通知,
    @Before("logPoinCut()")
    public void doBefore() {
        // 接收到请求,记录请求开始时间
        startTime.set(System.currentTimeMillis());
    }

    /**
     * @methodName:doAfterReturning
     * @description:正常返回通知,拦截用户操作日志,连接点正常执行完成后执行, 如果连接点抛出异常,则不会执行
     * @author:tanyp
     * @Params: [joinPoint, keys] 连接点
     * @Return: void
     * @editNote:
     * // 声明keys时指定的类型会限制目标方法必须返回指定类型的值或没有返回*值
		*	// 此处将rvt的类型声明为Object,意味着对目标方法的返回值不加限制
		*	1)pointcut/value:这两个属性的作用是一样的,它们都属于指定切入点对应的切入表达式。一样既可以是已有的切入点,也可直接定义切入点表达式。当指定了pointcut属性值后,value属性值将会被覆盖。
*2)returning:该属性指定一个形参名,用于表示Advice方法中可定义与此同名的形参,该形参可用于访问目标方法的返回值。除此之外,在Advice方法中定义该形参(代表目标方法的返回值)时指定的类型,会限制目标方法必须返回指定类型的值或没有返回值。   */
    @AfterReturning(value = "logPoinCut()", returning = "keys")
    public void doAfterReturning(JoinPoint joinPoint, Object keys) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        com.demo.domain.LogInfo logInfo = com.demo.domain.LogInfo.builder().build();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            // 获取切入点所在的方法
            Method method = signature.getMethod();

            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();

            // 获取操作
            Log log = method.getAnnotation(Log.class);
            if (Objects.nonNull(log)) {
                logInfo.setModule(log.modul());
                logInfo.setType(log.type());
                logInfo.setMessage(log.desc());
            }

            logInfo.setId(UUID.randomUUID().toString());
            logInfo.setMethod(className + "." + method.getName()); // 请求的方法名
            logInfo.setReqParam(JSON.toJSONString(converMap(request.getParameterMap()))); // 请求参数
            logInfo.setResParam(JSON.toJSONString(keys)); // 返回结果
            logInfo.setUserId(SecurityUserUtils.getUser().getId()); // 请求用户ID
            logInfo.setUserName(SecurityUserUtils.getUser().getUsername()); // 请求用户名称
            logInfo.setIp(IPUtils.getIpAddress(request)); // 请求IP
            logInfo.setUri(request.getRequestURI()); // 请求URI
            logInfo.setCreateTime(LocalDateTime.now()); // 创建时间
            logInfo.setVersion(version); // 操作版本
            logInfo.setTakeUpTime(System.currentTimeMillis() - startTime.get()); // 耗时
            logInfoService.save(logInfo);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @methodName:doAfterThrowing
     * @description:异常返回通知,用于拦截异常日志信息 连接点抛出异常后执行
     * @author:tanyp
     * @dateTime:2021/11/18 14:23
     * @Params: [joinPoint, e]
     * @Return: void
     * @editNote:
     */
    @AfterThrowing(pointcut = "exceptionLogPoinCut()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Throwable e) {
        // 获取RequestAttributes
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();

            // 获取切入点所在的方法
            Method method = signature.getMethod();

            // 获取请求的类名
            String className = joinPoint.getTarget().getClass().getName();

            logErrorInfoService.save(
                    LogErrorInfo.builder()
                            .id(UUID.randomUUID().toString())
                            .reqParam(JSON.toJSONString(converMap(request.getParameterMap()))) // 请求参数
                            .method(className + "." + method.getName()) // 请求方法名
                            .name(e.getClass().getName()) // 异常名称
                            .message(stackTraceToString(e.getClass().getName(), e.getMessage(), e.getStackTrace())) // 异常信息
                            .userId(SecurityUserUtils.getUser().getId()) // 操作员ID
                            .userName(SecurityUserUtils.getUser().getUsername()) // 操作员名称
                            .uri(request.getRequestURI()) // 操作URI
                            .ip(IPUtils.getIpAddress(request)) // 操作员IP
                            .version(version) // 版本号
                            .createTime(LocalDateTime.now()) // 发生异常时间
                            .build()
            );
        } catch (Exception e2) {
            e2.printStackTrace();
        }
    }

    /**
     * @methodName:converMap
     * @description:转换request 请求参数
     * @author:tanyp
     * @dateTime:2021/11/18 14:12
     * @Params: [paramMap]
     * @Return: java.util.Map<java.lang.String, java.lang.String>
     * @editNote:
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<String, String>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

    /**
     * @methodName:stackTraceToString
     * @description:转换异常信息为字符串
     * @author:tanyp
     * @dateTime:2021/11/18 14:12
     * @Params: [exceptionName, exceptionMessage, elements]
     * @Return: java.lang.String
     * @editNote:
     */
    public String stackTraceToString(String exceptionName, String exceptionMessage, StackTraceElement[] elements) {
        StringBuffer strbuff = new StringBuffer();
        for (StackTraceElement stet : elements) {
            strbuff.append(stet + "<br/>");
        }
        String message = exceptionName + ":" + exceptionMessage + "<br/>" + strbuff.toString();
        return message;
    }
}


相关知识

1. @Aspect 标记在类上 , 代表为切面类  
2. @Component 标记在类上,通过扫描 创建对象  
3. @Pointcut 标记在方法上,方法不需要返回值,不需要参数, 用来配置切入点表达式
4. @Before 前置增强,标记在方法上,参数指定切入点表达式对应的方法名  
5. @After 最终增强,标记在方法上,参数指定切入点表达式对应的方法名  
6. @AfterReturning 后置增强,标记在方法上,参数指定切入点表达式对应的方法名  
7. @AfterThrowing 异常增强,标记在方法上,参数指定切入点表达式对应的方法名  
8. @Around 环绕增强,一般单独使用,不会与前四种增强混合,标记在方法上,参数指定切入点表达式对应的方法名  
9. <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
  	开启aop的注解支持,
10. @EnableAspectJAutoProxy 纯注解时 开启aop注解支持  

springboot配置Aop不生效解决:

原因1.启动类没有扫描到Aop的类,也就是启动类或者AOP类的位置不对;修改位置或者@ComponentScan扫描
原因2.没有正确创建类; 正确的做法: 通过Java Class创建aspect类,然后加上 @Aspect和@Component注解。 我试了从Java Class创建aspect类,把之前无效的代码完整拷贝到这个新建的文件里,就生效了。至于上面那种创建aspect类为何会导致无效的内在原因,还不清楚,有知情的朋友请留言回复。 虽然两种创建方式不同,但最终代码呈现完全是一样的,就是一个有用一个无用。
原因3:以上两个方法没有解决我的问题: 最终是在 启动类上添加 @EnableAspectJAutoProxy 纯注解时 (开启aop注解支持 );
默认情况下SpringBoot是自动支持这个注解的,但是不知道为什么没用;

2022/08/02

  • 5
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP是一个强大的框架,可以帮助我们实现各种切面,其中包括日志记录。下面是实现日志记录的步骤: 1. 添加Spring AOP依赖 在Maven或Gradle中添加Spring AOP依赖。 2. 创建日志切面 创建一个用于记录日志的切面。这个切面可以拦截所有需要记录日志的方法。在这个切面中,我们需要使用@Aspect注解来声明这是一个切面,并使用@Pointcut注解来定义哪些方法需要被拦截。 ```java @Aspect @Component public class LoggingAspect { @Pointcut("execution(* com.example.demo.service.*.*(..))") public void serviceMethods() {} @Around("serviceMethods()") public Object logServiceMethods(ProceedingJoinPoint joinPoint) throws Throwable { // 获取方法名,参数列表等信息 String methodName = joinPoint.getSignature().getName(); Object[] args = joinPoint.getArgs(); // 记录日志 System.out.println("Method " + methodName + " is called with args " + Arrays.toString(args)); // 执行方法 Object result = joinPoint.proceed(); // 记录返回值 System.out.println("Method " + methodName + " returns " + result); return result; } } ``` 在上面的代码中,我们使用了@Around注解来定义一个环绕通知,它会在拦截的方法执行前后执行。在方法执行前,我们记录了该方法的名称和参数列表,然后在方法执行后记录了该方法的返回值。 3. 配置AOP 在Spring的配置文件中配置AOP。首先,我们需要启用AOP: ```xml <aop:aspectj-autoproxy/> ``` 然后,我们需要将创建的日志切面添加到AOP中: ```xml <bean id="loggingAspect" class="com.example.demo.aspect.LoggingAspect"/> <aop:config> <aop:aspect ref="loggingAspect"> <aop:pointcut id="serviceMethods" expression="execution(* com.example.demo.service.*.*(..))"/> <aop:around method="logServiceMethods" pointcut-ref="serviceMethods"/> </aop:aspect> </aop:config> ``` 在上面的代码中,我们将创建的日志切面声明为一个bean,并将其添加到AOP中。我们还定义了一个切入点,并将其与日志切面的方法进行关联。 4. 测试 现在,我们可以测试我们的日志记录功能了。在我们的业务逻辑中,所有匹配切入点的方法都会被拦截,并记录它们的输入和输出。我们可以在控制台中看到这些日志信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值