zoukankan      html  css  js  c++  java
  • 【SpringAop】【统一日志处理】注解方式理解以及使用

    @Pointcut("execution(public * com.jackie.springbootdemo.controller.HelloController.test*(..))")
    public void addAdvice(){}
    

    上面SpringBoot 配置 AOP 记录日志 可以通过切面的方式打印控制器层的日志,但是可能存在以下问题:
    不够灵活,由于是以所有 Controller 方法中的方法为切面,也就是说切死了,如果说我们不想让某个接口打印出入参日志,就办不到了;
    Controller 包层级过深时,导致很多包下的接口切不到。
    所以,就想通过指定某些方法打印日志,即通过自定义注解打印日志。
    添加所属依赖(依赖进来了aop就是默认开启了,不需要再启动类加启动注解或者在配置类中设置true)

    jar包依赖

    <dependencies>
        <!-- aop 依赖 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- json -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.57</version>
        </dependency>
    </dependencies>
    

    自定义日志注解WebLog

    @Retention(RetentionPolicy.RUNTIME) // 什么时候使用该注解,我们定义为运行时;
    @Target({ElementType.METHOD}) //用于什么地方,我们定义为作用于方法上;
    @Documented //注解是否将包含在 JavaDoc 中
    public @interface WebLog {
    String description() default "";
    }

    配置AOP切面
    @Aspect:声明该类为一个注解类;

    @Pointcut:定义一个切点,后面跟随一个表达式,表达式可以定义为切某个注解,也可以切某个 package 下的方法;

    @Before: 在切点之前,织入相关代码;

    @After: 在切点之后,织入相关代码;

    @AfterReturning: 在切点返回内容后,织入相关代码,一般用于对返回值做些加工处理的场景;

    @AfterThrowing: 用来处理当织入的代码抛出异常后的逻辑处理;

    @Around: 环绕,可以在切入点前后织入代码,并且可以自由的控制何时执行切点;

    申明这是一个切面

    @Aspect // 申明这是一个切面
    @Component // 使其被Spring扫描管理
    public class WebLogAspect {
    
        private final Logger logger = LoggerFactory.getLogger(getClass());
    
        /** 切入点 **/
        @Pointcut("@annotation(cn.van.annotation.annotation.WebLog)")
        public void webLogPointcut(){}
        // 在执行方法前后调用Advice,相当于@Before和@AfterReturning一起做的事儿;
        @Around("webLogPointcut()")
        public Object doAround(ProceedingJoinPoint pjp) throws Throwable{
            Long startTime = System.currentTimeMillis();
            // 接收到请求,记录请求内容
            ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            HttpServletRequest request = attributes.getRequest();
            // 获取注解的属性值
            WebLog webLog = ((MethodSignature)pjp.getSignature()).getMethod().getAnnotation(WebLog.class);
            logger.info("请求方法描述:" + webLog.description() );
            logger.info("请求开始时间:"+ LocalDateTime.now());
            // 记录下请求内容
            logger.info("请求Url : " + request.getRequestURL().toString());
            logger.info("请求方式 : " + request.getMethod());
            logger.info("请求ip : " + request.getRemoteAddr());
            logger.info("请求方法 : " + pjp.getSignature().getDeclaringTypeName() + "." + pjp.getSignature().getName());
            logger.info("请求参数 : " + Arrays.toString(pjp.getArgs()));
            Object obj = pjp.proceed();
            logger.info("请求结束时间:"+ LocalDateTime.now());
            logger.info("请求返回 : " + JSON.toJSONString(obj));
            logger.info("日志耗时:{} ms",(System.currentTimeMillis() - startTime));
            return obj;
    

    测试类

    @RestController
    @RequestMapping("/")
    public class WebLogTestController {
    
        /**
         * 一般请求,不需打印日志
         * @return
         */
        @GetMapping("/sayHello")
        public String sayHello() {
            return "Hello";
        }
    
        /**
         * 需要打印日志的请求
         * @param str
         * @return
         */
        @GetMapping("/webLog")
        @WebLog(description = "这里是方法描述")
        public String webLogTest(String str) {
            return "成功返回:" + str;
    

    测试结果

    2019-06-11 18:26:15.732  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求方法描述:这里是方法描述
    2019-06-11 18:26:15.741  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求开始时间:2019-06-11T18:26:15.740
    2019-06-11 18:26:15.741  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求Url : http://localhost:8080/webLog
    2019-06-11 18:26:15.741  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求方式 : GET
    2019-06-11 18:26:15.741  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求ip : 0:0:0:0:0:0:0:1
    2019-06-11 18:26:15.742  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求方法 : cn.van.annotation.web.controller.WebLogTestController.webLogTest
    2019-06-11 18:26:15.742  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求参数 : ["nice"]
    2019-06-11 18:26:15.749  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求结束时间:2019-06-11T18:26:15.749
    2019-06-11 18:26:15.802  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 请求返回 : "成功返回:"nice""
    2019-06-11 18:26:15.803  INFO 5207 --- [nio-8080-exec-1] cn.van.annotation.aspect.WebLogAspect    : 日志耗时:73 ms
    

    小结

    如果不想在生产环境中打印日志,只想在开发环境或者测试环境中使用,只需为切面(WebLogAspect)添加@Profile就可以了,如下

    @Aspect
    @Component
    //@Profile("dev") //指定dev环境该注解有效,其他环境无效
    public class WebLogAspect {
    	...
    }
    

    多切面指定优先级
    实际情况下我们的服务中可能不止定义了一个切面,比如说我们针对 Web 层的接口,不仅要打印日志,还要校验 token;那么,我们可以通过 @Order(i) 注解来指定优先级。
    规律:
    在切点之前, @Order 从小到大被执行,也就是说越小的优先级越高;
    在切点之后, @Order 从大到小被执行,也就是说越大的优先级越高;

    优秀的代码块展示

    package com.linln.component.actionLog.annotation;
    
    import com.linln.component.actionLog.action.base.BaseActionMap;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 行为日志注解
     * @author 小懒虫
     * @date 2018/11/12
     */
    @Retention(RetentionPolicy.RUNTIME)
    @Target({ElementType.METHOD})
    public @interface ActionLog {
        // 日志名称
        String name() default "";
        // 日志消息
        String message() default "";
        // 行为key
        String key() default "";
        // 行为类
        Class<? extends BaseActionMap> action() default BaseActionMap.class;
    }
    
    
    package com.linln.component.actionLog.annotation;
    
    import com.linln.common.utils.SpringContextUtil;
    import com.linln.component.actionLog.action.base.BaseActionMap;
    import com.linln.component.actionLog.action.base.ResetLog;
    import com.linln.component.actionLog.action.model.ActionModel;
    import com.linln.component.actionLog.action.model.BusinessMethod;
    import com.linln.component.actionLog.action.model.BusinessType;
    import com.linln.component.shiro.ShiroUtil;
    import com.linln.modules.system.domain.ActionLog;
    import com.linln.modules.system.service.ActionLogService;
    import lombok.extern.slf4j.Slf4j;
    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.stereotype.Component;
    import org.springframework.util.Assert;
    
    import java.lang.reflect.Method;
    
    /**
     * 行为日志注解AOP
     * @author 小懒虫
     * @date 2018/11/12
     */
    @Aspect
    @Component
    @Slf4j
    public class ActionLogAop {
        private final static String DEFAULT_ACTION_NAME = "default";
    
        @Pointcut("@annotation(com.linln.component.actionLog.annotation.ActionLog)")
        public void actionLog() {};
    
        @Around("actionLog()")
        public Object recordLog(ProceedingJoinPoint point) throws Throwable {
            // 先执行切入点,获取返回值
            Object proceed = point.proceed();
    
    
            /* 读取ActionLog注解消息 */
            Method targetMethod = ((MethodSignature)(point.getSignature())).getMethod();
            com.linln.component.actionLog.annotation.ActionLog anno =
                    targetMethod.getAnnotation(com.linln.component.actionLog.annotation.ActionLog.class);
            // 获取name值
            String name = anno.name();
            // 获取message值
            String message = anno.message();
            // 获取key值
            String key = anno.key();
            // 获取行为模型
            Class<? extends BaseActionMap> action = anno.action();
            BaseActionMap instance = action.newInstance();
            Object actionModel = instance.get(!key.isEmpty() ? key : DEFAULT_ACTION_NAME);
            Assert.notNull(actionModel, "无法获取日志的行为方法,请检查:"+point.getSignature());
    
    
            // 封装日志实例对象
            ActionLog actionLog = new ActionLog();
            actionLog.setIpaddr(ShiroUtil.getIp());
            actionLog.setClazz(point.getTarget().getClass().getName());
            actionLog.setMethod(targetMethod.getName());
            actionLog.setType(((ActionModel) actionModel).getType());
            actionLog.setName(!name.isEmpty() ? name : ((ActionModel) actionModel).getName());
            actionLog.setMessage(message);
            actionLog.setOperBy(ShiroUtil.getSubject());
            if(ShiroUtil.getSubject() != null){
                actionLog.setOperName(ShiroUtil.getSubject().getNickname());
            }
    
            //判断是否为普通实例对象
            if(actionModel instanceof BusinessType){
                actionLog.setMessage(((BusinessType) actionModel).getMessage());
            }else {
                // 重置日志-自定义日志数据
                ResetLog resetLog = new ResetLog();
                resetLog.setActionLog(actionLog);
                resetLog.setRetValue(proceed);
                resetLog.setJoinPoint(point);
                try {
                    Method method = action.getDeclaredMethod(((BusinessMethod)actionModel).getMethod(), ResetLog.class);
                    method.invoke(instance, resetLog);
                    if(!resetLog.getRecord()) {
                        return proceed;
                    }
                } catch (NoSuchMethodException e) {
                    log.error("获取行为对象方法错误!请检查方法名称是否正确!", e);
                    e.printStackTrace();
                }
            }
    
    
            // 保存日志
            ActionLogService actionLogService = SpringContextUtil.getBean(ActionLogService.class);
            actionLogService.save(actionLog);
    
            return proceed;
        }
    }
    
    
  • 相关阅读:
    就业DAY7_web服务器_tcp三次握手四次挥手,返回浏览器需要的页面http服务器
    就业DAY7_web服务器_http协议
    就业DAY6_web服务器_正则表达式
    就业DAY5_多任务_协程
    就业DAY5_多任务_进程,进程池,队列
    win10安装ubuntu系统,报错WslRegisterDistribution failed with error: 0x8007019e
    解决ubuntu与win10双系统时间不同步
    Linux常用压缩解压命令
    ubuntu添加国内源
    解决Ubuntu“下载额外数据文件失败 ttf-mscorefonts-installer”的问题 (转载)
  • 原文地址:https://www.cnblogs.com/fangh816/p/13305989.html
Copyright © 2011-2022 走看看