当前位置: 首页 > news >正文

【Spring】小白速通AOP-日志记录Demo

这篇文章我将通过一个最常用的AOP场景-方法调用日志记录,带你彻底理解AOP的使用。例子使用Spring Boot+Spring AOP实现。
如果对你有帮助可以点个赞和关注。谢谢大家的支持!!

一、Demo实操步骤:

1.首先添加Maven依赖

<!-- Spring AOP支持 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2.创建日志切面类

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

@Aspect     // 标识这是一个切面类
@Component  // 让Spring能够扫描到这个组件
public class LoggingAspect {
    
    // 创建日志记录器
    private final Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 定义切点:拦截service包下的所有方法
     * execution(返回值类型 包名.类名.方法名(参数列表))
     */
    @Pointcut("execution(* com.example.demo.service.*.*(..))")
    public void serviceLayer() {}

    /**
     * 前置通知:在目标方法执行前执行
     */
    @Before("serviceLayer()")
    public void logBefore(JoinPoint joinPoint) {
        // joinPoint包含目标方法的信息
        logger.info("准备执行 {} 方法", joinPoint.getSignature().getName());
        logger.info("参数: {}", Arrays.toString(joinPoint.getArgs()));
    }

    /**
     * 后置通知:在目标方法正常返回后执行
     */
    @AfterReturning(pointcut = "serviceLayer()", returning = "result")
    public void logAfterReturning(JoinPoint joinPoint, Object result) {
        logger.info("方法 {} 执行成功,返回值: {}", 
            joinPoint.getSignature().getName(), result);
    }

    /**
     * 异常通知:在目标方法抛出异常后执行
     */
    @AfterThrowing(pointcut = "serviceLayer()", throwing = "ex")
    public void logAfterThrowing(JoinPoint joinPoint, Exception ex) {
        logger.error("方法 {} 执行异常: {}", 
            joinPoint.getSignature().getName(), ex.getMessage());
    }

    /**
     * 环绕通知:最强大的通知类型,可以控制方法执行
     */
    @Around("serviceLayer()")
    public Object logAround(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        
        logger.info("进入方法: {}", joinPoint.getSignature().getName());
        
        try {
            // 执行目标方法
            Object result = joinPoint.proceed();
            
            long elapsedTime = System.currentTimeMillis() - start;
            logger.info("方法执行完成,耗时: {} ms", elapsedTime);
            
            return result;
        } catch (Exception e) {
            long elapsedTime = System.currentTimeMillis() - start;
            logger.error("方法执行异常,耗时: {} ms,异常: {}", 
                elapsedTime, e.getMessage());
            throw e;
        }
    }
}

3.创建测试服务类

@Service
public class UserService {
    
    public String getUserById(Long id) {
        // 模拟数据库查询
        if (id == 1) {
            return "用户张三";
        } else if (id == 2) {
            return "用户李四";
        }
        throw new RuntimeException("用户不存在");
    }
    
    public void updateUser(String user) {
        // 模拟更新操作
        System.out.println("更新用户: " + user);
    }
}

4.测试Controller

@RestController
public class UserController {
    
    @Autowired
    private UserService userService;
    
    @GetMapping("/user/{id}")
    public String getUser(@PathVariable Long id) {
        return userService.getUserById(id);
    }
    
    @PostMapping("/user")
    public void updateUser(@RequestBody String user) {
        userService.updateUser(user);
    }
}

二、代码讲解:

1.切面(Aspect):

LoggingAspect类 就是一个切面,它封装了横切关注点(这里是日志记录)。

2.切点(Pointcut):

@Pointcut("execution(* com.example.demo.service.*.*(..))")
  • 定义了"哪些方法需要被拦截"
  • execution是匹配方法执行的连接点
  • *表示任意返回值
  • com.example.demo.service指定包名
  • 第一个*表示所有类
  • 第二个*表示所有方法
  • (. .)表示任意参数

3.通知(Advice):

  • @Before方法执行前
  • @AfterReturning:方法正常返回后
  • @AfterThrowing:方法抛出异常后
  • @Around:最强大,可以控制方法是否执行

三、执行流程演示:

场景1:调用GET/user/1

1.请求进入UserController.getUser(1)
2.调用userService.getUserById(1)时被AOP拦截
3.执行顺序:

  • @Around的开始部分(记录开始时间)
  • @Before(记录方法准备执行)
  • 实际执行getUserById方法
  • @AfterReturning(记录成功返回)
  • @Around的结束部分(计算耗时)

控制台输出 执行结果:

进入方法: getUserById
准备执行 getUserById 方法
参数: [1]
方法 getUserById 执行成功,返回值: 用户张三
方法执行完成,耗时: 12 ms

场景2.调用GET/user/3(会抛出异常)

1.请求进入UserController.getUser(3)
2.调用userService.getUserById(3)时被AOP拦截
3.执行顺序:

  • @Around的开始部分
  • @Before
  • 执行getUserById抛出异常
  • @AfterThrowing(记录异常信息)
  • @Around的异常处理部分

控制台输出 执行结果:

进入方法: getUserById
准备执行 getUserById 方法
参数: [3]
方法 getUserById 执行异常: 用户不存在
方法执行异常,耗时: 5 ms,异常: 用户不存在

四、为什么要用AOP?

对比传统写法,如果不用AOP,我们需要在每个方法中写日志代码,重复代码太多,显得冗杂。

@Service
public class UserService {
    
    private final Logger logger = LoggerFactory.getLogger(this.getClass());
    
    public String getUserById(Long id) {
        long start = System.currentTimeMillis();
        logger.info("准备执行 getUserById 方法");
        logger.info("参数: " + id);
        
        try {
            if (id == 1) {
                String result = "用户张三";
                logger.info("方法执行成功,返回值: " + result);
                return result;
            }
            throw new RuntimeException("用户不存在");
        } catch (Exception e) {
            logger.error("方法执行异常: " + e.getMessage());
            throw e;
        } finally {
            long elapsedTime = System.currentTimeMillis() - start;
            logger.info("方法耗时: " + elapsedTime + " ms");
        }
    }
}

此时就可以使用AOP来解决。

AOP的优势:

1.消除重复代码:日志逻辑只写一次,应用到所有方法。
2.业务更纯净:业务方法只关注核心逻辑。
3.维护方便:修改日志格式只需改切面类。
4.灵活扩展:可以随时添加新的横切逻辑(如权限检查)。

五、应用场景:

1. 统一日志记录(如上面的例子)

2.性能监控:统计方法执行时间

@Around("serviceLayer()")
public Object monitorPerformance(ProceedingJoinPoint jp) throws Throwable {
    long start = System.currentTimeMillis();
    Object result = jp.proceed();
    long elapsed = System.currentTimeMillis() - start;
    
    if (elapsed > 500) { // 超过500ms记录警告
        logger.warn("方法 {} 执行耗时 {} ms", jp.getSignature(), elapsed);
    }
    return result;
}

3.事务管理(Spring的@Transactional就是基于AOP实现的)

4.权限控制:

@Before("@annotation(requiresAuth)")
public void checkAuth(JoinPoint jp, RequiresAuth requiresAuth) {
    if (!SecurityUtils.hasRole(requiresAuth.value())) {
        throw new AccessDeniedException("无权限访问");
    }
}

5.缓存处理:

@Around("@annotation(cacheable)")
public Object handleCache(ProceedingJoinPoint jp, Cacheable cacheable) throws Throwable {
    String key = generateKey(jp, cacheable);
    Object cached = cache.get(key);
    if (cached != null) return cached;
    
    Object result = jp.proceed();
    cache.put(key, result);
    return result;
}

六、小白常见问题:

1.切面不生效?

  • 确保切面类@Component 注解。
  • 确保启动类有 @EnableAspectJAutoProxy(Spring Boot自动配置)
  • 检查切点表达式是否正确匹配到目标方法。

2.执行顺序问题:

  • 多个切面同一个切点,可以用 @Order注解指定顺序。
  • 单个切面中通知执行顺序:Around前 → Before → 方法执行 → Around后 → AfterReturning/AfterThrowing → After

3.自调用问题:

  • 同一个类内部方法调用不会触发AOP(因为不是代理对象调用)
  • 解决方案:从Spring容器获取代理对象调用

七、总结:

通过这篇文章,你应该理解了:

1.AOP如何通过切面、切点、通知等概念工作。
2.为什么AOP能解决代码重复的问题。
3.如何在Spring项目中实际使用AOP。
4.AOP的各种实际应用场景。

AOP就像是一个"方法拦截器",在不修改原有代码的情况下,给方法添加各种增强功能。这是Spring框架的核心特性之一,掌握后能大幅提高代码质量和开发效率。

看到这里,如果觉得这篇文章对你有帮助,能给up主点个赞嘛,编写不易,谢谢大家的支持与鼓励!!

下篇文章挖掘AOP代码实现中的一些细节问题。

相关文章:

  • 通信协议详解(九):SENT协议 —— 汽车传感器的“摩斯电码大师”
  • 01.win10/win11安装jdk,保姆级详解拆分步骤及命令的意义和报错解决方案
  • 基于Pyhon的京东笔记本电脑数据可视化分析系统
  • 非常适合做后台项目的go脚手架
  • API安全:构建安全可靠的数据交互基础
  • JS 执行机制
  • 屏幕空间反射SSR-笔记
  • 高效网页截图利器:支持长截图、异步加载内容截图、API调用、Docker一键部署!
  • 电网电能质量分析:原理、算法及实际应用
  • Linux驱动开发进阶(五)- 系统调用
  • Logo语言的死锁
  • 【C++】类和对象 (第一弹)
  • 处理语言模型返回的响应
  • 【Survival Analysis】【机器学习】【1】
  • Android 11.0 framework系统首次开机添加锁屏壁纸的功能
  • Go语言报错总结(文章持续更新)
  • 洛谷蓝桥杯刷题
  • CRC校验码的检错性能(三)——基于对偶码重量分布计算漏检概率
  • STM32江科大----IIC
  • 004 Vue Cli脚手架(vue2)
  • 泰州网站建设/危机舆情公关公司
  • iis做网站上传速度慢/网站搜索工具
  • 公司宣传推广方案/南宁seo服务公司
  • 重庆的公需科目在哪个网站做/上海aso优化公司
  • 做网站镜像步骤/优化课程体系
  • 网站里的地图定位怎么做的/搜索引擎优化seo专员招聘