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

本文涉及的产品
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
日志服务 SLS,月写入数据量 50GB 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日志并进行多维度分析。
相关文章
|
27天前
|
Rust 前端开发 JavaScript
Tauri 开发实践 — Tauri 日志记录功能开发
本文介绍了如何为 Tauri 应用配置日志记录。Tauri 是一个利用 Web 技术构建桌面应用的框架。文章详细说明了如何在 Rust 和 JavaScript 代码中设置和集成日志记录,并控制日志输出。通过添加 `log` crate 和 Tauri 日志插件,可以轻松实现多平台日志记录,包括控制台输出、Webview 控制台和日志文件。文章还展示了如何调整日志级别以优化输出内容。配置完成后,日志记录功能将显著提升开发体验和程序稳定性。
60 1
Tauri 开发实践 — Tauri 日志记录功能开发
|
26天前
|
Java API 数据安全/隐私保护
(工作经验)优雅实现接口权限校验控制:基于自定义注解、AOP与@ConditionalOnProperty配置开关的通用解决方案
(工作经验)优雅实现接口权限校验控制:基于自定义注解、AOP与@ConditionalOnProperty配置开关的通用解决方案
52 1
|
24天前
|
XML Java 数据格式
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
本文介绍了如何使用Spring框架的注解方式实现AOP(面向切面编程)。当目标对象没有实现接口时,Spring会自动采用CGLIB库进行动态代理。文中详细解释了常用的AOP注解,如`@Aspect`、`@Pointcut`、`@Before`等,并提供了完整的示例代码,包括业务逻辑类`User`、配置类`SpringConfiguration`、切面类`LoggingAspect`以及测试类`TestAnnotationConfig`。通过这些示例,展示了如何在方法执行前后添加日志记录等切面逻辑。
59 2
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
|
7天前
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
25 1
|
1月前
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
48 2
|
2月前
|
存储 监控 数据可视化
SLS 虽然不是直接使用 OSS 作为底层存储,但它凭借自身独特的存储架构和功能,为用户提供了一种专业、高效的日志服务解决方案。
【9月更文挑战第2天】SLS 虽然不是直接使用 OSS 作为底层存储,但它凭借自身独特的存储架构和功能,为用户提供了一种专业、高效的日志服务解决方案。
136 9
|
2月前
|
Shell Python
salt自定义模块内使用日志例子
salt自定义模块内使用日志例子
|
3月前
|
监控 Serverless 开发者
函数计算发布功能问题之查看函数的调用日志的问题如何解决
函数计算发布功能问题之查看函数的调用日志的问题如何解决
|
2天前
|
XML 安全 Java
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
本文介绍了Java日志框架的基本概念和使用方法,重点讨论了SLF4J、Log4j、Logback和Log4j2之间的关系及其性能对比。SLF4J作为一个日志抽象层,允许开发者使用统一的日志接口,而Log4j、Logback和Log4j2则是具体的日志实现框架。Log4j2在性能上优于Logback,推荐在新项目中使用。文章还详细说明了如何在Spring Boot项目中配置Log4j2和Logback,以及如何使用Lombok简化日志记录。最后,提供了一些日志配置的最佳实践,包括滚动日志、统一日志格式和提高日志性能的方法。
64 30
【日志框架整合】Slf4j、Log4j、Log4j2、Logback配置模板
|
28天前
|
XML JSON Java
Logback 与 log4j2 性能对比:谁才是日志框架的性能王者?
【10月更文挑战第5天】在Java开发中,日志框架是不可或缺的工具,它们帮助我们记录系统运行时的信息、警告和错误,对于开发人员来说至关重要。在众多日志框架中,Logback和log4j2以其卓越的性能和丰富的功能脱颖而出,成为开发者们的首选。本文将深入探讨Logback与log4j2在性能方面的对比,通过详细的分析和实例,帮助大家理解两者之间的性能差异,以便在实际项目中做出更明智的选择。
176 3