来一手 AOP 注解方式进行日志记录

简介:

   系统日志对于定位/排查问题的重要性不言而喻,相信许多开发和运维都深有体会。

   通过日志追踪代码运行状况,模拟系统执行情况,并迅速定位代码/部署环境问题。

   系统日志同样也是数据统计/建模的重要依据,通过分析系统日志能窥探出许多隐晦的内容。

   如系统的健壮性(服务并发访问/数据库交互/整体响应时间...)

   某位用户的喜好(分析用户操作习惯,推送对口内容...)

   当然系统开发者还不满足于日志组件打印出来的日志,毕竟冗余且篇幅巨长。

   so,对于关键的系统操作设计日志表,并在代码中进行操作的记录,配合 SQL 统计和搜索数据是件很愉快的事情。

   本篇旨在总结在 Spring 下使用 AOP 注解方式进行日志记录的过程,如果能对你有所启发阁下不甚感激。

1. 依赖类库

       <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>${aspectjweaver.version}</version>
        </dependency>

  AspectJ 中的很多语法结构基本上已成为 AOP 领域的标准。

  Spring 也有自己的 Spring-AOP,采用运行时生成代理类,底层可以选用 JDK 或者 CGLIB 动态代理。

  通俗点,AspectJ 在编译时增强要切入的类,而 Spring-AOP 是在运行时通过代理类增强切入的类,效率和性能可想而知。

  Spring 在 2.0 的时候就已经开始支持 AspectJ ,现在到 4.X 的时代已经很完美的和 AspectJ 拥抱到了一起。

  开启扫描 AspectJ 注解的支持:

    <!-- proxy-target-class等于true是强制使用cglib代理,proxy-target-class默认false,如果你的类实现了接口 就走JDK代理,如果没有,走cglib代理  -->
    <!-- 注:对于单利模式建议使用cglib代理,虽然JDK动态代理比cglib代理速度快,但性能不如cglib -->
    <aop:aspectj-autoproxy proxy-target-class="true"/>

2. 定义切入点日志注解

   

   目标操作日志表,其中设计了一些必要的字段,具体字段请拿捏具体项目场景,根据表结构设计注解如下。

复制代码
@Inherited
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface OperationLog {

    String operationModular() default "";

    String operationContent() default "";
}
复制代码

   上述我只做了两个必要的参数,一个为操作的模块,一个为具体的操作内容。

   其实根据项目场景这里参数的设计可以非常丰富,不被其他程序员吐槽在此一举。

3. 编写处理日志切点类

   @Pointcut("@annotation(com.rambo.spm.common.aop.OperationLog)")
    public void operationLogAspect() {

    }

   类的构造函数上描述了该类要拦截的为 OperationLog 的注解方法, 同样你也可以配置 XML 进行拦截。

   切入点的姿势有很多,不仅是正则同样也支持组合表达式,强大的表达式能让你精准的切入到任何你想要的地方。

   更多详情:http://blog.csdn.net/zhengchao1991/article/details/53391244

   看到这里如果你对 Spring AOP 数据库事务控制熟悉,其实 Spring AOP 记录日志是相似的机制。

复制代码
    @Before("operationLogAspect()")
    public void doBefore(JoinPoint joinPoint) {
        logger.info("before aop:{}", joinPoint);
        //do something
    }

    @Around("operationLogAspect()")
    public Object doAround(ProceedingJoinPoint point) {
        logger.info("Around:{}", point);
        Object proceed = null;
        try {
            proceed = point.proceed();

            //do somthing
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            logger.error("日志 aop 异常信息:{}", throwable.getMessage());
        }
        return proceed;
    }

    @AfterThrowing("operationLogAspect()")
    public void doAfterThrowing(JoinPoint pjp) {
        logger.info("@After:{}", pjp);
        //do somthing
    }

    @After("operationLogAspect()")
    public void doAfter(JoinPoint pjp) {
        logger.info("@After:{}", pjp);
    }

    @AfterReturning("operationLogAspect()")
    public void doAfterReturning(JoinPoint point) {
        logger.info("@AfterReturning:{}", point);
    }
复制代码

    AspectJ 提供了几种通知方法,通过在方法上注解这几种通知,解析对应的方法入参,你就能洞悉切点的一切运行情况。

   前置通知(@Before):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常);

   返回后通知(@AfterReturning):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回;

   抛出异常后通知(@AfterThrowing):方法抛出异常退出时执行的通知;

   后通知(@After):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出);

   环绕通知(@Around):包围一个连接点(joinpoint)的通知,如方法调用;

   通知方法中的值与构造函数一致,指定该通知对哪个切点有效,

   上述 @Around  为最强大的一种通知类型,可以在方法调用前后完成自定义的行为,它可选择是否继续执行切点、直接返回、抛出异常来结束执行。

   @Around 之所以如此强大是和它的入参有关,别的注解注解入参只容许 JoinPoint ,而 @Around 注解容许入参 ProceedingJoinPoint。

复制代码
package org.aspectj.lang;

import org.aspectj.runtime.internal.AroundClosure;

public interface ProceedingJoinPoint extends JoinPoint {
    void set$AroundClosure(AroundClosure var1);

    Object proceed() throws Throwable;

    Object proceed(Object[] var1) throws Throwable;
}
复制代码

   反编译 ProceedingJoinPoint 你会恍然大悟,Proceedingjoinpoint 继承了 JoinPoint 。

   在 JoinPoint 的基础上暴露出 proceed 这个方法。proceed 方法很重要,这是 aop 代理链执行的方法。

   暴露出这个方法,就能支持 aop:around 这种切面(而其他的几种切面只需要用到 JoinPoint,这跟切面类型有关), 能决定是否走代理链还是走自己拦截的其他逻辑。

   如果项目没有特定的需求,妥善使用 @Around 注解就能帮你解决一切问题。

复制代码
    @Around("operationLogAspect()")
    public Object doAround(ProceedingJoinPoint point) {
        logger.info("Around:{}", point);
        Object proceed = null;
        try {
            proceed = point.proceed();

            Object pointTarget = point.getTarget();
            Signature pointSignature = point.getSignature();

            String targetName = pointTarget.getClass().getName();
            String methodName = pointSignature.getName();
            Method method = pointTarget.getClass().getMethod(pointSignature.getName(), ((MethodSignature) pointSignature).getParameterTypes());
            OperationLog methodAnnotation = method.getAnnotation(OperationLog.class);
            String operationModular = methodAnnotation.operationModular();
            String operationContent = methodAnnotation.operationContent();

            OperationLogPO log = new OperationLogPO();
            log.setOperUserid(SecureUtil.simpleUUID());
            log.setOperUserip(HttpUtil.getClientIP(getHttpReq()));
            log.setOperModular(operationModular);
            log.setOperContent(operationContent);
            log.setOperClass(targetName);
            log.setOperMethod(methodName);
            log.setOperTime(new Date());
            log.setOperResult("Y");
            operationLogService.insert(log);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
            logger.error("日志 aop 异常信息:{}", throwable.getMessage());
        }
        return proceed;
    }
复制代码

   别忘记将上面切点处理类/和要切入的类托管给 Spring,Aop 日志是不是很简单,复杂的应该是 aspectj 内部实现机制,有机会要看看源码哦。

   处理切点类完整代码:

  View Code

   上述三步骤之后,你就可以在想记录日志的方法上面添加注解来进行记录操作日志,像下面这样。


本文转自Orson博客园博客,原文链接:http://www.cnblogs.com/java-class/p/7261937.html,如需转载请自行联系原作者

相关实践学习
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
【涂鸦即艺术】基于云应用开发平台CAP部署AI实时生图绘板
相关文章
|
12月前
|
XML 安全 Java
使用 Spring 的 @Aspect 和 @Pointcut 注解简化面向方面的编程 (AOP)
面向方面编程(AOP)通过分离横切关注点,如日志、安全和事务,提升代码模块化与可维护性。Spring 提供了对 AOP 的强大支持,核心注解 `@Aspect` 和 `@Pointcut` 使得定义切面与切入点变得简洁直观。`@Aspect` 标记切面类,集中处理通用逻辑;`@Pointcut` 则通过表达式定义通知的应用位置,提高代码可读性与复用性。二者结合,使开发者能清晰划分业务逻辑与辅助功能,简化维护并提升系统灵活性。Spring AOP 借助代理机制实现运行时织入,与 Spring 容器无缝集成,支持依赖注入与声明式配置,是构建清晰、高内聚应用的理想选择。
927 0
|
SQL 监控 Java
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
这篇文章介绍了如何在IDEA和Spring Boot中使用AOP技术实现日志信息的记录到数据库的详细步骤和代码示例。
在IDEA 、springboot中使用切面aop实现日志信息的记录到数据库
|
Java API 数据安全/隐私保护
(工作经验)优雅实现接口权限校验控制:基于自定义注解、AOP与@ConditionalOnProperty配置开关的通用解决方案
(工作经验)优雅实现接口权限校验控制:基于自定义注解、AOP与@ConditionalOnProperty配置开关的通用解决方案
863 1
|
存储 安全 Java
Spring Boot 3 集成Spring AOP实现系统日志记录
本文介绍了如何在Spring Boot 3中集成Spring AOP实现系统日志记录功能。通过定义`SysLog`注解和配置相应的AOP切面,可以在方法执行前后自动记录日志信息,包括操作的开始时间、结束时间、请求参数、返回结果、异常信息等,并将这些信息保存到数据库中。此外,还使用了`ThreadLocal`变量来存储每个线程独立的日志数据,确保线程安全。文中还展示了项目实战中的部分代码片段,以及基于Spring Boot 3 + Vue 3构建的快速开发框架的简介与内置功能列表。此框架结合了当前主流技术栈,提供了用户管理、权限控制、接口文档自动生成等多项实用特性。
1344 8
|
XML Java 数据格式
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
本文介绍了如何使用Spring框架的注解方式实现AOP(面向切面编程)。当目标对象没有实现接口时,Spring会自动采用CGLIB库进行动态代理。文中详细解释了常用的AOP注解,如`@Aspect`、`@Pointcut`、`@Before`等,并提供了完整的示例代码,包括业务逻辑类`User`、配置类`SpringConfiguration`、切面类`LoggingAspect`以及测试类`TestAnnotationConfig`。通过这些示例,展示了如何在方法执行前后添加日志记录等切面逻辑。
1621 2
使用完全注解的方式进行AOP功能实现(@Aspect+@Configuration+@EnableAspectJAutoProxy+@ComponentScan)
|
JSON Java 数据库
SpringBoot项目使用AOP及自定义注解保存操作日志
SpringBoot项目使用AOP及自定义注解保存操作日志
509 1
|
缓存 NoSQL Java
Springboot自定义注解+aop实现redis自动清除缓存功能
通过上述步骤,我们不仅实现了一个高度灵活的缓存管理机制,还保证了代码的整洁与可维护性。自定义注解与AOP的结合,让缓存清除逻辑与业务逻辑分离,便于未来的扩展和修改。这种设计模式非常适合需要频繁更新缓存的应用场景,大大提高了开发效率和系统的响应速度。
925 2
|
XML Java 数据库
Spring5入门到实战------10、操作术语解释--Aspectj注解开发实例。AOP切面编程的实际应用
这篇文章是Spring5框架的实战教程,详细解释了AOP的关键术语,包括连接点、切入点、通知、切面,并展示了如何使用AspectJ注解来开发AOP实例,包括切入点表达式的编写、增强方法的配置、代理对象的创建和优先级设置,以及如何通过注解方式实现完全的AOP配置。
|
11月前
|
监控 Java Spring
AOP 切面编程
AOP(面向切面编程)通过动态代理在不修改源码的前提下,对方法进行增强。核心概念包括连接点、通知、切入点、切面和目标对象。常用于日志记录、权限校验、性能监控等场景,结合Spring AOP与@Aspect、@Pointcut等注解,实现灵活的横切逻辑管理。
2449 6
AOP 切面编程
|
11月前
|
XML Java 数据格式
《深入理解Spring》:AOP面向切面编程深度解析
Spring AOP通过代理模式实现面向切面编程,将日志、事务等横切关注点与业务逻辑分离。支持注解、XML和编程式配置,提供五种通知类型及丰富切点表达式,助力构建高内聚、低耦合的可维护系统。