0

0

Java中如何用AOP实现日志切面

穿越時空

穿越時空

发布时间:2025-06-18 18:12:02

|

626人浏览过

|

来源于php中文网

原创

aop日志切面通过分离横切逻辑提升代码可维护性,其解决方案步骤如下:1. 添加spring aop与aspectj依赖;2. 创建使用@aspect与@component注解的切面类;3. 使用@pointcut定义切点拦截指定包下的方法;4. 通过@before、@after定义前置与后置通知记录方法出入日志;5. 在配置类中使用@enableaspectjautoproxy启用aop;6. 配置slf4j等日志框架;7. 使用@afterthrowing处理异常并记录错误信息;8. 利用threadlocal传递用户id等上下文信息并在@after清理以避免内存泄漏;9. 优化性能时应精确切点表达式、使用异步日志、降低日志级别、选用高效日志框架、避免复杂拼接、结合@around实现条件日志。

Java中如何用AOP实现日志切面

AOP(面向切面编程)在Java中用于将横切关注点(如日志、安全等)从核心业务逻辑中分离出来。通过AOP,你可以避免在每个方法中都编写重复的日志代码,从而提高代码的可维护性和可读性。

Java中如何用AOP实现日志切面

解决方案

Java中如何用AOP实现日志切面

使用Spring AOP实现日志切面,步骤如下:

立即学习Java免费学习笔记(深入)”;

Java中如何用AOP实现日志切面
  1. 添加依赖:确保你的项目中包含了Spring AOP和AspectJ的依赖。

    
        org.springframework
        spring-aop
        5.3.9
    
    
        org.aspectj
        aspectjweaver
        1.9.7
    
  2. 创建日志切面类:创建一个类,使用@Aspect注解将其声明为一个切面。

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class LoggingAspect {
    
        private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
        @Pointcut("execution(* com.example.service.*.*(..))")
        public void serviceMethods() {}
    
        @Before("serviceMethods()")
        public void logBefore(JoinPoint joinPoint) {
            logger.info("Entering method: " + joinPoint.getSignature().toShortString());
            Object[] args = joinPoint.getArgs();
            if (args != null && args.length > 0) {
                logger.info("with arguments: " + java.util.Arrays.toString(args));
            }
        }
    
        @After("serviceMethods()")
        public void logAfter(JoinPoint joinPoint) {
            logger.info("Exiting method: " + joinPoint.getSignature().toShortString());
        }
    }
  3. 定义切点(Pointcut):使用@Pointcut注解定义一个切点,指定哪些方法需要被拦截。这里,我们拦截com.example.service包下所有类的所有方法。

  4. 定义通知(Advice):使用@Before@After等注解定义通知,指定在切点方法执行前、后执行哪些操作。@Before用于在方法执行前记录日志,@After用于在方法执行后记录日志。

  5. 启用AOP:在Spring配置类中启用AOP。

    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.EnableAspectJAutoProxy;
    
    @Configuration
    @EnableAspectJAutoProxy
    public class AppConfig {
        // ... other configurations
    }
  6. 配置Logger:使用SLF4J和Logback或Log4j等日志框架,配置日志输出。

日志切面配置完成后,所有com.example.service包下的方法在执行前后都会自动记录日志。

AOP日志切面如何处理异常?

在AOP切面中处理异常,可以使用@AfterThrowing通知。它允许你在目标方法抛出异常后执行一些操作,比如记录异常信息。

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class LoggingAspect {

    private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);

    @AfterThrowing(pointcut = "execution(* com.example.service.*.*(..))", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Throwable ex) {
        logger.error("Exception in " + joinPoint.getSignature().toShortString() +
                     " with message = " + ex.getMessage(), ex);
    }
}

在这个例子中,logAfterThrowing方法会在com.example.service包下的方法抛出异常时被调用。throwing = "ex"指定了异常对象的名字,可以在方法中访问该异常。

AOP日志切面如何传递上下文信息?

Artbreeder
Artbreeder

创建令人惊叹的插画和艺术

下载

有时需要在日志中包含一些上下文信息,比如用户ID、请求ID等。可以使用ThreadLocal来传递这些信息。

  1. 创建ThreadLocal:创建一个ThreadLocal来存储上下文信息。

    public class ContextHolder {
        private static final ThreadLocal userId = new ThreadLocal<>();
    
        public static void setUserId(String id) {
            userId.set(id);
        }
    
        public static String getUserId() {
            return userId.get();
        }
    
        public static void clear() {
            userId.remove();
        }
    }
  2. 在切面中设置和获取上下文信息:在切面的@Before通知中设置上下文信息,在需要记录日志的地方获取。

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class LoggingAspect {
    
        private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
        @Before("execution(* com.example.service.*.*(..))")
        public void logBefore(JoinPoint joinPoint) {
            // 假设从请求头中获取用户ID
            String userId = "someUserId"; // 实际场景中从Request上下文中获取
            ContextHolder.setUserId(userId);
    
            logger.info("User " + ContextHolder.getUserId() + " entering method: " + joinPoint.getSignature().toShortString());
        }
    }
  3. 清理ThreadLocal:为了避免内存泄漏,在请求处理完成后,需要清理ThreadLocal。可以使用@After通知来清理。

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class LoggingAspect {
    
        private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
        @After("execution(* com.example.service.*.*(..))")
        public void logAfter(JoinPoint joinPoint) {
            logger.info("Exiting method: " + joinPoint.getSignature().toShortString());
            ContextHolder.clear();
        }
    }

AOP日志切面的性能影响如何优化?

AOP会引入额外的处理步骤,因此可能会对性能产生影响。优化AOP日志切面性能的一些方法:

  1. 精确的切点定义:避免使用过于宽泛的切点表达式,只拦截需要记录日志的方法。例如,避免使用execution(* com.example..*(..)),而是使用更具体的表达式,如execution(* com.example.service.*.*(..))

  2. 异步日志记录:将日志记录操作放入异步线程中执行,避免阻塞主线程。可以使用java.util.concurrent.ExecutorService或Spring的@Async注解来实现异步日志记录。

    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.scheduling.annotation.Async;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class LoggingAspect {
    
        private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
        @After("execution(* com.example.service.*.*(..))")
        @Async
        public void logAfterAsync(JoinPoint joinPoint) {
            logger.info("Exiting method: " + joinPoint.getSignature().toShortString());
        }
    }

    需要确保在Spring配置中启用了异步支持:

    import org.springframework.context.annotation.Configuration;
    import org.springframework.scheduling.annotation.EnableAsync;
    
    @Configuration
    @EnableAsync
    public class AppConfig {
        // ... other configurations
    }
  3. 减少日志级别:在生产环境中,使用较低的日志级别(如INFO、WARN、ERROR),避免记录过多的调试信息。

  4. 使用高效的日志框架:选择性能较好的日志框架,如Logback。

  5. 避免在日志中进行复杂的计算:尽量避免在日志消息中使用复杂的字符串拼接或计算,可以先将结果计算好再记录。

  6. 使用条件日志:只有在满足特定条件时才记录日志,例如,只有当方法执行时间超过一定阈值时才记录日志。

    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.Around;
    import org.aspectj.lang.annotation.Aspect;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.stereotype.Component;
    
    @Aspect
    @Component
    public class LoggingAspect {
    
        private static final Logger logger = LoggerFactory.getLogger(LoggingAspect.class);
    
        @Around("execution(* com.example.service.*.*(..))")
        public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
            long start = System.currentTimeMillis();
            Object proceed = joinPoint.proceed();
            long executionTime = System.currentTimeMillis() - start;
    
            if (executionTime > 100) { // 超过100ms才记录日志
                logger.warn(joinPoint.getSignature() + " executed in " + executionTime + "ms");
            }
            return proceed;
        }
    }

通过上述方法,可以有效地优化AOP日志切面的性能,使其对系统性能的影响降到最低。

相关专题

更多
java
java

Java是一个通用术语,用于表示Java软件及其组件,包括“Java运行时环境 (JRE)”、“Java虚拟机 (JVM)”以及“插件”。php中文网还为大家带了Java相关下载资源、相关课程以及相关文章等内容,供大家免费下载使用。

832

2023.06.15

java正则表达式语法
java正则表达式语法

java正则表达式语法是一种模式匹配工具,它非常有用,可以在处理文本和字符串时快速地查找、替换、验证和提取特定的模式和数据。本专题提供java正则表达式语法的相关文章、下载和专题,供大家免费下载体验。

737

2023.07.05

java自学难吗
java自学难吗

Java自学并不难。Java语言相对于其他一些编程语言而言,有着较为简洁和易读的语法,本专题为大家提供java自学难吗相关的文章,大家可以免费体验。

734

2023.07.31

java配置jdk环境变量
java配置jdk环境变量

Java是一种广泛使用的高级编程语言,用于开发各种类型的应用程序。为了能够在计算机上正确运行和编译Java代码,需要正确配置Java Development Kit(JDK)环境变量。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

397

2023.08.01

java保留两位小数
java保留两位小数

Java是一种广泛应用于编程领域的高级编程语言。在Java中,保留两位小数是指在进行数值计算或输出时,限制小数部分只有两位有效数字,并将多余的位数进行四舍五入或截取。php中文网给大家带来了相关的教程以及文章,欢迎大家前来阅读学习。

398

2023.08.02

java基本数据类型
java基本数据类型

java基本数据类型有:1、byte;2、short;3、int;4、long;5、float;6、double;7、char;8、boolean。本专题为大家提供java基本数据类型的相关的文章、下载、课程内容,供大家免费下载体验。

446

2023.08.02

java有什么用
java有什么用

java可以开发应用程序、移动应用、Web应用、企业级应用、嵌入式系统等方面。本专题为大家提供java有什么用的相关的文章、下载、课程内容,供大家免费下载体验。

430

2023.08.02

java在线网站
java在线网站

Java在线网站是指提供Java编程学习、实践和交流平台的网络服务。近年来,随着Java语言在软件开发领域的广泛应用,越来越多的人对Java编程感兴趣,并希望能够通过在线网站来学习和提高自己的Java编程技能。php中文网给大家带来了相关的视频、教程以及文章,欢迎大家前来学习阅读和下载。

16925

2023.08.03

Java 桌面应用开发(JavaFX 实战)
Java 桌面应用开发(JavaFX 实战)

本专题系统讲解 Java 在桌面应用开发领域的实战应用,重点围绕 JavaFX 框架,涵盖界面布局、控件使用、事件处理、FXML、样式美化(CSS)、多线程与UI响应优化,以及桌面应用的打包与发布。通过完整示例项目,帮助学习者掌握 使用 Java 构建现代化、跨平台桌面应用程序的核心能力。

36

2026.01.14

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
成为PHP架构师-自制PHP框架
成为PHP架构师-自制PHP框架

共28课时 | 2.4万人学习

YMP在线手册
YMP在线手册

共64课时 | 35.8万人学习

ThinkPHP6.x 微实战--十天技能课堂
ThinkPHP6.x 微实战--十天技能课堂

共26课时 | 1.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号