自定义注解+AOP实现记录日志功能

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: 自定义注解+AOP实现记录日志功能

使用注解可以大大减少开发的代码量,所以在实际项目的开发中会使用到很多的注解。特别是做一些公共基础的功能,比如日志记录,事务管理,权限控制这些功能,使用注解会非常高效且优雅。


对于自定义注解,主要有三个步骤,定义注解,标记注解,解析注解,其实并不是很难。所以关于自定义注解的更多内容就不在这里展开说了,大家也可以看我之前的文章Java注解Annotation小结


正如文章标题所言,通过自定义注解+AOP切面编程实现日志记录功能,开发实现流程主要分为下面四步:


  • 1 定义注解
  • 2 标记注解:在对应需要记录日志的方法上标记注解
  • 3 解析注解:编写切面类,进行相应处理逻辑
  • 4 开启切面能力


下面实战一下,自定义一个注解@DoLog,用于方法上,当方法被调用时即打印日志,在控制台显示调用方传入的参数和调用返回的结果。


1 定义注解


首先定义注解@DoLog,在方法上使用,为了能在反射中读取注解信息,当然是设置为RUNTIME

@Target(value = ElementType.METHOD)
@Documented
@Retention(value = RetentionPolicy.RUNTIME)
public @interface DoLog {
    String dataStatus() default "";
    String[] labelNames() default {};
}


说明:这里注解里面定义的属性,对于记录日志没有实际意义,这里只是顺便演示一下如何在切面类中获取注解中的属性值。


2 标记注解:在对应需要记录日志的方法上标记注解


注意:使用切面的注解不能加在私有方法上,否则切入不了。这个要注意一下,你也可以自己试一下。

@RestController
@Slf4j
public class DemoController {
    @GetMapping("/demo2/{orderno}")
    @DoLog(dataStatus = "1",labelNames = {"name","age"})
    public BaseResult<String> test1(@PathVariable(value = "orderno") String orderno) {
        return BaseResult.success("aaa");
    }
}


3 解析注解:编写切面类,进行相应处理逻辑


最关键的一步来了,解析注解,一般在项目中会使用Spring的AOP技术解析注解,当然如果只需要解析一次的话,也可以使用Spring处理器BeanPostProcessor来读取注解,相关内容可以参考我的文章使用Spring的BeanPostProcessor优雅的实现工厂模式

咱这里的场景是打印每次方法被调用的日志,所以使用AOP比较合适。

创建一个切面类DoLogAspect进行解析。

@Aspect
@Slf4j
@Component
public class DoLogAspect {
    //切面点为标记了@DoLog注解的方法
    @Pointcut("@annotation(cn.test.util.DoLog)")
    public void doLog() {
    }
    //环绕通知
    @Around("doLog()")
    @SuppressWarnings("unchecked")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        long starTime = System.currentTimeMillis();
        //通过反射获取被调用方法的Class
        Class type = joinPoint.getSignature().getDeclaringType();
        //获取类名
        String typeName = type.getSimpleName();
        //方法名
        String methodName = joinPoint.getSignature().getName();
        //获取参数列表
        Object[] args = joinPoint.getArgs();
        //参数Class的数组
        Class[] clazz = new Class[args.length];
        for (int i = 0; i < args.length; i++) {
            clazz[i] = args[i].getClass();
        }
        //通过反射获取调用的方法method
        Method method = type.getMethod(methodName, clazz);
        DoLog doLog = method.getAnnotation(DoLog.class);
        log.info("注解参数:{},{}",logApi.dataStatus(),JSONObject.toJSONString(logApi.labelNames()));
        //获取方法的参数
        Parameter[] parameters = method.getParameters();
        //拼接字符串,格式为{参数1:值1,参数2::值2}
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            String name = parameter.getName();
            sb.append(name).append(":").append(args[i]).append(",");
        }
        if (sb.length() > 0) {
            sb.deleteCharAt(sb.lastIndexOf(","));
        }
        //执行结果
        Object res;
        try {
            //执行目标方法,获取执行结果
            res = joinPoint.proceed();
            log.info("调用{}.{}方法成功,参数为[{}],返回结果[{}]", typeName, methodName, sb.toString(), JSONObject.toJSONString(res));
        } catch (Exception e) {
            log.error("调用{}.{}方法发生异常", typeName, methodName);
            //如果发生异常,则抛出异常
            throw e;
        } finally {
            log.info("调用{}.{}方法,耗时{}ms", typeName, methodName, (System.currentTimeMillis() - starTime));
        }
        //返回执行结果
        return res;
    }
}


注意:自己在主逻辑之外使用切面加入的处理逻辑,一定要try处以免影响到正常业务逻辑。


扩展(Pointcut定义):

public class DoLogAspect {
    //切面点为标记了@DoLog注解的方法
    @Pointcut("@annotation(cn.test.util.DoLog)")
    public void doLog() {
    }
    //环绕通知
    @Around("doLog()")
    @SuppressWarnings("unchecked")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //处理代码
    }


以上代码同样可以替换为下面的代码:

public class DoLogAspect {
    //环绕通知
    @Around("@annotation(cn.test.util.DoLog)")
    @SuppressWarnings("unchecked")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        //处理代码
    }


4 开启切面能力


定义完切面类后,就需要在启动类添加启动AOP的注解。

@SpringBootApplication
//添加此注解,开启AOP
@EnableAspectJAutoProxy
public class DemoApplication {
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
}


5 测试


启动项目,请求接口,我们可以看到控制台出现被调用方法的日志信息,如下:

2021-11-13 06:16:26.617|||INFO|||-|||-|||RMI TCP Connection(2)-192.168.0.100|||DispatcherServlet--->Completed initialization in 22 ms
2021-11-13 06:30:08.807|||INFO|||-|||-|||http-nio-8084-exec-1|||DoLogAspect--->注解参数:1,["name","age"]
2021-11-13 06:30:08.823|||INFO|||-|||-|||http-nio-8084-exec-1|||DoLogAspect--->调用DemoController.test1方法成功,参数为[arg0:1001],返回结果[{"data":"aaa","msg":"success","ret":"0"}]
2021-11-13 06:30:08.823|||INFO|||-|||-|||http-nio-8084-exec-1|||DoLogAspect--->调用DemoController.test1方法,耗时20ms


总结


这种使用注解和切面编程,记录接口请求参数和返回值的功能,因为使用起来特别方便,而且代码侵入性又特别小,所以在实际项目中基本上都会使用,而最常见的场景就是日志、监控打点,异常收口。


关于自定义注解+AOP实现记录日志功能就分享到这里,期待能够给有缘的同学带来一些启发和帮助。

相关实践学习
日志服务之使用Nginx模式采集日志
本文介绍如何通过日志服务控制台创建Nginx模式的Logtail配置快速采集Nginx日志并进行多维度分析。
相关文章
|
2月前
|
Rust 前端开发 JavaScript
Tauri 开发实践 — Tauri 日志记录功能开发
本文介绍了如何为 Tauri 应用配置日志记录。Tauri 是一个利用 Web 技术构建桌面应用的框架。文章详细说明了如何在 Rust 和 JavaScript 代码中设置和集成日志记录,并控制日志输出。通过添加 `log` crate 和 Tauri 日志插件,可以轻松实现多平台日志记录,包括控制台输出、Webview 控制台和日志文件。文章还展示了如何调整日志级别以优化输出内容。配置完成后,日志记录功能将显著提升开发体验和程序稳定性。
114 1
Tauri 开发实践 — Tauri 日志记录功能开发
|
2月前
|
Java API 数据安全/隐私保护
(工作经验)优雅实现接口权限校验控制:基于自定义注解、AOP与@ConditionalOnProperty配置开关的通用解决方案
(工作经验)优雅实现接口权限校验控制:基于自定义注解、AOP与@ConditionalOnProperty配置开关的通用解决方案
76 1
|
2月前
|
XML Java 数据格式
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
本文介绍了如何使用Spring框架的注解方式实现AOP(面向切面编程)。当目标对象没有实现接口时,Spring会自动采用CGLIB库进行动态代理。文中详细解释了常用的AOP注解,如`@Aspect`、`@Pointcut`、`@Before`等,并提供了完整的示例代码,包括业务逻辑类`User`、配置类`SpringConfiguration`、切面类`LoggingAspect`以及测试类`TestAnnotationConfig`。通过这些示例,展示了如何在方法执行前后添加日志记录等切面逻辑。
282 2
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
|
1月前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
45 1
|
2月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
75 2
|
3月前
|
存储 监控 数据可视化
SLS 虽然不是直接使用 OSS 作为底层存储,但它凭借自身独特的存储架构和功能,为用户提供了一种专业、高效的日志服务解决方案。
【9月更文挑战第2天】SLS 虽然不是直接使用 OSS 作为底层存储,但它凭借自身独特的存储架构和功能,为用户提供了一种专业、高效的日志服务解决方案。
175 9
|
3月前
|
Shell Python
salt自定义模块内使用日志例子
salt自定义模块内使用日志例子
|
4月前
|
存储 监控 Serverless
函数计算发布功能问题之用户在使用主流函数计算产品的日志服务时可能会遇到使用成本的问题如何解决
函数计算发布功能问题之用户在使用主流函数计算产品的日志服务时可能会遇到使用成本的问题如何解决
|
4月前
|
监控 Serverless 开发者
函数计算发布功能问题之查看函数的调用日志的问题如何解决
函数计算发布功能问题之查看函数的调用日志的问题如何解决
|
1月前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
270 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板