springboot利用aop实现日志记录

一、前言:

我们的日常开发中,一些重要的操作或者错误是需要记录到日志表中方便我们的查阅;如果我们每个业务中都写记录日志显然很傻也不现实,那我们就可以用到面向切面aop了,做一个切面,然后把代码在业务中切进去即可。

到底什么是aop呢?AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。(百度百科)。在日常开发当中经常用来日志记录,性能统计,安全控制,事务处理,异常处理等等。

二、引入依赖:



    org.springframework.boot
    spring-boot-starter-aop

三、编写注解类:

package com.rails.erpinterface.aop;

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface OperateLogAop {

    /**
     * 操作类型
     * @return
     */
    String operateType() default "无声明类型";

    /**
     * 操作说明
     * @return
     */
    String operateNote() default "无声明说明";
}
@Target 声明这是一个自定义注解类,ElementType.METHOD 表明此注解可声明在方法上
@Retention 声明注解保留期限,RetentionPolicy.RUNTIME 表明此注解可保留至运行时,可以通过反射获取注解信息

四、编写切面类:

package com.rails.erpinterface.aop.operateLog;

import com.rails.erpinterface.aop.OperateLogAop;
import com.rails.erpinterface.common.CommonUser;
import com.rails.erpinterface.constant.CommonConstant;
import com.rails.erpinterface.core.entity.OperateLog;
import com.rails.erpinterface.core.service.IOperateLogService;
import com.rails.erpinterface.utils.ToolUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.Date;

/**
 * @author 乔瑞
 * @version 1.0
 * @date 2019/7/30 11:28
 */
@Aspect
@Component
@Slf4j
public class OperateLogAspect {

    @Autowired
    private IOperateLogService operateLogService;

    //在注解的位置切入代码
    @Pointcut("@annotation( com.rails.erpinterface.aop.OperateLogAop )")
    public void Pointcut() {
    }

    //@Around:环绕通知
    @Around("Pointcut()")
    //此处返回值一定要返回方法的返回值
    public Object Around(ProceedingJoinPoint pjp) throws Throwable {
        OperateLog operateLog = new OperateLog();
        operateLog.setOperateLogId(ToolUtil.getUUID());
        //获取目标类名称
        operateLog.setClassName(pjp.getTarget().getClass().getName());
        //获取目标类方法名称
        operateLog.setMethodName(pjp.getSignature().getName());
        //入参
        operateLog.setInputParameter(StringUtils.join(pjp.getArgs()));
        //记录方法开始执行时间
        Long start = System.currentTimeMillis();
        //方法执行
        Object result = pjp.proceed();
        //捕获日志异常,防止影响正常业务,日志出现异常不应该影响正常业务
        try {
            //方法执行用时
            operateLog.setOperateConsume((System.currentTimeMillis() - start) + "");
            //返回值
            operateLog.setOperateResult(result.toString());
            operateLog.setOperateTime(new Date());
            operateLog.setSystemType(CommonConstant.SYSTEM_TYPE.INTERFACE);
            //获取注解属性
            OperateLogAop operateLogAop = ((MethodSignature) pjp.getSignature()).getMethod().getAnnotation(OperateLogAop.class);
            operateLog.setOperateType(operateLogAop.operateType());
            operateLog.setOperateNote(operateLogAop.operateNote());
            operateLog.setOperateUser(CommonUser.me().getLoginName());

            operateLogService.save(operateLog);
        } catch (Exception e) {
            log.error("[日志存储:业务日志存储]:失败,错误信息:", e);
        }

        return result;
    }

    /*
    //统一切点,对com.rails.erpinterface.core.controller及其子包中所有的类的所有方法切面
    @Pointcut("execution(public * com.rails.erpinterface.core.controller..*.*(..))")
    public void Pointcut() {
    }

    //前置通知
    @Before("Pointcut()")
    public void beforeMethod(JoinPoint joinPoint){
        log.info("调用了前置通知");
    }

    //@After: 后置通知
    @After("Pointcut()")
    public void afterMethod(JoinPoint joinPoint){
        log.info("调用了后置通知");
    }
    //@AfterRunning: 返回通知 rsult为返回内容
    @AfterReturning(value="Pointcut()",returning="result")
    public void afterReturningMethod(JoinPoint joinPoint, Object result){
        log.info("调用了返回通知");
    }
    //@AfterThrowing: 异常通知
    @AfterThrowing(value="Pointcut()",throwing="e")
    public void afterReturningMethod(JoinPoint joinPoint, Exception e){
        log.info("调用了异常通知");
    }*/

}
@Aspect 表明是一个切面类
@Component 将当前类注入到Spring容器内
@Pointcut 切入点,其中execution用于使用切面的连接点。使用方法:execution(方法修饰符(可选) 返回类型 方法名 参数 异常模式(可选)) ,可以使用通配符匹配字符,*可以匹配任意字符。
@Before 在方法前执行
@After 在方法后执行
@AfterReturning 在方法执行后返回一个结果后执行
@AfterThrowing 在方法执行过程中抛出异常的时候执行
@Around 环绕通知,就是可以在执行前后都使用,这个方法参数必须为ProceedingJoinPoint,proceed()方法就是被切面的方法,上面四个方法可以使用JoinPoint,JoinPoint包含了类名,被切面的方法名,参数等信息。

五、结束:

这样我们就可以通过切面记录到表中了,用法是在需要的方法上加上注解即可。

你可能感兴趣的:(Java框架)