开发者

Springboot使用@Aspect、自定义注解记录日志方式

目录
  • 1、前言
  • 2、切面方法说明
  • 3、AOP日志记录方式
    • 3.1、扫描包的方式
    • 3.2、自定义注解方式
      • 3.2.1.Maven依赖
      • 3.2.2. 自定义注解
      • 3.2.3. TakeTimeASPect(使用AOP技术统计方法执行前后消耗时间)
      • 3.2.4. 在接口方法上加上注解
      • 3.2.5. 打印查看接口耗时
  • 总结

    1、前言

    日志的作用不言而喻,协助运维故障排查,问题分析,数据统计,记录查询等。

    • 故障排查:通过日志可对系统进行实时健康度监控,系统日志记录程序 Syslog 就是为这个目的而设计的。
    • 数据分析:通过对业务系统日志进行关联分析,可以掌握业务系统的整体运行情况,并可通过日志进一步掌握用户画像、用户访问地域、用户访问热点资源等信息,从而为业务平台的市场营销、销售策略等提供数据支撑。
    • 安全合规审计:根据国家网络安全法等级保护要求,需要对安全设备日志进行集中存储和分析。
    • 内网安全监控:很多企业的信息泄露源于内部,使用日志进行用户行为分析以监控内网安全,已成为行业共识。
    • 智能运维:随着大数据时代的到来,数据管理和分析方案越来越智能,自动化运维已逐渐普及。机器数据作为智能运维的基础数据,必将发挥越来越重要的作用。

    日志各位大佬记录都会,但是规范有效记录日志的很少,本文为工具类文章,开箱即用,直接导入项目即可,CV大法一键搞定

    2、切面方法说明

    • @Aspect -- 作用是把当前类标识为一个切面供容器读取
    • @Pointcut -- (切入点):就是带有通知的连接点,在程序中主要体现为书写切入点表达式
    • @Before -- 标识一个前置增强方法,相当于BeforeAdvice的功能
    • @AfterReturning -- 后置增强,相当于AfterReturningAdvice,方法退出时执行
    • @AfterThrowing -- 异常抛出增强,相当于ThrowsAdvice
    • @After -- final增强,不管是抛出异常或者正常退出都会执行
    • @Around -- 环绕增强,相当于MethodInterceptor

    AOP五种通知工作

    • 前置通知:在目标方法调用之前执行,可以获得切入点信息;
    • 后置通知:在目标方法执行后执行,目标方法有异常不执行;
    • 异常通知:在目标方法抛出异常时执行,可以获取异常信息;
    • 最终通知:在目标方法执行后执行,无论是否有异常都执行;
    • 环绕通知:最强大的通知类型,在目标方法执行前后操作,可以阻止目标方法执行。

    3、AOP日志记录方式

    aop解决的这个办法有很多种,这里介绍两种最简单、最常用的

    • 扫描包的方式。传入的参数在请求头里面(企业常用)
    • 自定义注解

    3.1、扫描包的方式

    1)添加依赖

    <--AOP的依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
    </dependency>
    <--jsON的依赖-->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.79</version>
    </dependency>
    <--mysql-data-jpa的依赖-->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-data-jpa</artifactId>
    </dependency>
    <--lombok的依赖-->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    
    /**   
     * Copyright  2022 eSunny Info. Tech Ltd. All rights reserved.
     * 功能描述:
     * @Title: WebLogAspect.Java  
     * @Package com.police.violation.aop  
     * @Description: TODO  
     * @author Administrator   
     * @date 2022年8月31日 上午9:48:11  
     * @version  
     */
    
    package com.police.violation.aop;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.servlet.http.HttpServletRequest;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
    import org.springframework.web.multipart.MultipartFile;
    
    import com.google.gson.Gson;
    
    /**
     * @author www.exception.site (exception 教程网)
     * @date 2019/2/12
     * @time 14:03
     * @discription
     **/
    @Aspect
    @Component
    public class WebLogAspect {
    
    	private final static Logger logger = LoggerFactory.getLogger(WebLogAspect.class);
    
    	/** 以 controller 包下定义的所有请求为切入点 */
    	@Pointcut("execution(public * com.police.violation.controller..*.*Ticket(..))")
    	public void webLog() {
    	}
    
    	/**
    	 * 在切点之前织入
    	 * 
    	 * @param joinPoint
    	 * @throws Throwable
    	 */
    	@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("HTTP Method    : {}", request.getMethod());
    		// 打印调用 controller 的全路径以及执行方法
    		logger.info("Class Method   : {}.{}", joinPoint.getSignature().getDeclaringTypeName(),
    				joinPoinphpt.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  : {}", new Gson().toJson(result));
            // 执行耗时
            logger.info("Time-Consuming : {} ms", System.currentTimeMillis() - startTime);
            return result;
        }
    
    
    	/**
    	 *
    	 * @param proceedingJoinPoint 切面
    	 * @return
    	 * @throws Throwable
    	 */
    //	@Around("webLog()")
    //	public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    //		long start = System.currentTimeMillis();
    //		Object result = proceedingJoinPoint.proceed();
    //		logger.info("Request Params       : {}", getRequestParams(proceedingJoinPoint));
    //		logger.info("Result               : {}", result);
    //		logger.info("Time Cost            : {} ms", System.currentTimeMillis() - start);
    //
    //		return result;
    //js	}
    
    	/**
    	 * 获取入参
    	 * 
    	 * @param proceedingJoinPoint
    	 *
    	 * @return
    	 */
    	private Map<String, Object> getRequestParams(ProceedingJoinPoint proceedingJoinPoint) {
    		Map<String, Object> requestParams = new HashMap<>();
    
    		// 参数名
    		String[] paramNames = ((MethodSignature) proceedingJoinPoint.getSignature()).getParameterNames();
    		// 参数值
    		Object[] paramValues = proceedingJoinPoint.getArgs();
    
    		for (int i = 0; i < paramNames.length; i++) {
    			Object value = paramValues[i];
    
    			// 如果是文件对象
    			if (value instanceof MultipartFile) {
    				MultipartFile file = (MultipartFile) value;
    				// 获取文件名
    				value = file.getOriginalFilename();
    			}
    
    			requestParams.put(paramNames[i], value);
    		}
    
    		return requestParams;
    	}
    
    }

    Springboot使用@Aspect、自定义注解记录日志方式

    3.2、自定义注解方式

    3.2.1.Maven依赖

    <!--引入AOP依赖-->
     <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-aop</artifactId>
     </dependency>

    3.2.2. 自定义注解

    package com.ruoyi.aspect;
    
    import java.lang.annotation.*;
     
    /**
     * 统计耗时
     */
    @Documented //用于描述其它类型的annotation应该被作为被标注的程序成员的公共API,因此可以被例如javadoc此类的工具文档化.Documented是一个标记注解,没有成员.
    @Target(ElementType.METHOD) //指定被修饰的Annotation可以放置的位置(被修饰的目标)类,方法,属性
    @Retention(RetentionPolicy.RUNTIME) //定义注解的保留策略, RetentionPolicy.RUNTIME:注解会在class字节码文件中存在,在运行时可以通过反射获取到
    public @interface TakeTime {
     
        String methodName() default "";
    }

    3.2.3. TakeTimeAspect(使用AOP技术统计方法执行前后消耗时间)

    package com.ruoyi.aspect;
    
    import com.alibaba.fastjson.JSON;
    import lombok.extern.slf4j.Slf4j;
    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.springframework.stereotype.Component;
    import org.springframework.web.context.request.RequestContextHolder;
    import org.springframework.web.context.request.ServletRequestAttributes;
     
    import javax.servlet.http.HttpServletRequest;
    import java.util.Date;
     
    /**
     * 耗时统计
     */
    @Slf4j
    @Aspect
    @Component
    public class TakeTimeAspect {
        //统计请求的处理时间
        ThreadLocal<Long> startTime = new ThreadLocal<>();
        ThreadLocal<Long> endTime = new ThreadLocal<>();
     
        /**
         * 带有@TakeTime注解的方法
         */
    //    @Pointcut("within(com.lwx.backend.user.controller.*)")
    //    @Pointcut("execution(* com.lwx.backend.user.controller.*.*(..))")
        @Pointcut("@annotation(com.ruoyi.aspect.TakeTime)")
        public void TakeTime() {
     
        }
     
        //    @Before("within(com.lwx.backend.user.controller.*)")
        @Before("TakeTime()")
        public void doBefore(JoinPoint joinPoint) throws Throwable {
            // 获取方法的名称
            String methodName = joinPoint.getSignature().getName();
            // 获取方法入参
            Object[] param = joinPoint.getArgs();
            StringBuilder sb = new StringBuilder();
            for (Object o : param) {
                sb.append(o + ";");
            }
            log.info("进入《{}》 方法,参数为: {}", methodName,sb.toString());
     
            System.out.println("System.currentTimeMillis(): "+System.currentTimeMillis());
            System.out.println("new Date(): "+new Date());
            startTime.set(System.currentTimeMillis());
            log.info("方法开始时间:" +startTime.get());
            //接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
     
            //记录请求的内容
            log.info("请求URL:" + request.getRequestURL().toString());
            log.info("请求METHOD:" + request.getMethod());
        }
     
        //    @AfterReturning(returning = "ret", pointcut = "within(com.lwx.backend.user.controller.*)")
        @AfterReturning(returning = "ret", pointcut = "TakeTime()")
        public USWufvoid doAfterReturning(Object ret) {
            //处理完请求后,返回内容
            log.info("方法返回值:" + JSON.toJSONString(ret));
            endTime.set(System.currentTimeMillis());
            log.info("方法结束时间" +endTime.get());
            log.info("方法结束时间" +new Date());
            log.info("方法执行时间:" + (endTime.get() - startTime.get()));
        }
    }

    3.2.4. 在接口方法上加上注解

    @RequestMapping("/loadForTestVariableCategories")
    @TakeTime(methodName = "loadForTestVariableCategories")
    public void loadForTestVariableCategories(HttpServletRequest req, HttpServletResponse resp) throws Exception {
       KnowledgeBase knowledgeBase = buildKnowledgeBase(req);
       List<VariableCategory> vcs=knowledgeBase.getResourceLibrary().getVariableCategories();
       httpSessionKnowledgeCache.put(req, RuleConstant.VCS_KEY, vcs);
       writeObjectToJson(resp, vcs);
    }

    3.2.5. 打印查看接口耗时

    2022-07-30 22:40:47.057 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 进入《queryUserList》 方法,参数为: {pageNum=1, pageSize=10, userName=张三};

    System.currentTimeMillis(): 1659192047058

    new Date(): Sat Jul 30 22:40:47 CST 2022

    2022-07-30 22:40:47.058 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法开始时间:1659192047058

    2022-07-30 22:40:47.059 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 请求URL:http://localhost:8080/user/queryUserList

    2022-07-30 22:40:47.059 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 请求METHOD:POST

    com.lwx.common.aspect.TakeTimeAspect : 方法返回值:{"data":{"endRow":1,"hasNextPage":false,"hasPreviousPage":false,"isFirstPage":true,"isLastPage":true,"list":[{"comment":"男","userAddress":"广东","userAge":20,"userBirth":1640966400000,"userId":1,"userName":"张三","userSex":"男"}]php,"navigateFirstPage":1,"navigateLastPage":1,"navigatePages":8,"navigatepageNums":[1],"nextPage":0,"pageNum":1,"pageSize":10,"pages":1,"prePage":0,"size":1,"startRow":1,"total":1},"message":"success","status":100,"timestamp":1659192047819}

    2022-07-30 22:40:47.846 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法结束时间1659192047846

    2022-07-30 22:40:47.846 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法结束时间Sat Jul 30 22:40:47 CST 2022

    2022-07-30 22:40:47.846 INFO 16276 --- [nio-8080-exec-2] com.lwx.common.aspect.TakeTimeAspect : 方法执行时间:788

    注:

    • 如果接口引用的注解失效可能是配置注解的类是在别的包下面
    • 需要在启动类上设置扫描注解类的包
    @SpringBootApplication(scanBasePackages {"com.Lwx.backend","com.Lwx.common"})

    总结

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程客栈(www.devze.com)。

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜