AOP-面向切面编程

本文详细介绍了如何在Spring中使用AOP实现全局日志记录,包括@Before、@After、@AfterReturning和@AfterThrowing注解的应用,并展示了如何通过@Around实现环绕通知。通过实例演示了这些注解在方法执行前后及异常处理中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

	Spring有AOP和IOC两大特性,本篇主要讲AOP的实战使用。主要从以下的例子来说明aop的使用。

AOP实现全局日志记录

/**
 * 控制日志aop记录类
 * author zy
 * createdate 2022-02-16
 */
@Aspect
@Configuration
public class ControllerLogAspect {

    private static final Log logger = LogFactory.getLog(ControllerLogAspect.class);

    //抽取公共的切入点表达式,此表达式标识其作用域作用于controller层下的代码
    @Pointcut("execution(public String com.zy.main.controller.*.*(..))")
    public void pointCut() {

    }

    ;

    /**
     * 在方法执行前
     * @param joinPoint
     */
    @Before("pointCut()")
    public void logStart(JoinPoint joinPoint) {
        //获取参数
        Object[] args = joinPoint.getArgs();
        //joinPoint.getSignature().getDeclaringTypeName()类名
        //joinPoint.getSignature().getName() 获取到请求方法名
        logger.info(joinPoint.getSignature().getDeclaringTypeName()+joinPoint.getSignature().getName() +"运行。。。@Before:参数列表是"+ Arrays.asList(args));
    }

    /**
     * 在方法执行后执行
     * @param joinPoint
     */
    @After("com.zy.main.aspect.ControllerLogAspect.pointCut()")
    public void logEnd(JoinPoint joinPoint) {

        Object[] args = joinPoint.getArgs();
        //joinPoint.getSignature().getDeclaringTypeName()类名
        //joinPoint.getSignature().getName() 获取到请求方法名
        logger.info(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName() +"结束。。。@After:参数列表是"+ Arrays.asList(args));

    }

    //JoinPoint一定要出现在参数表的第一位
    @AfterReturning(value = "pointCut()", returning = "result")
    public void logReturn(JoinPoint joinPoint, Object result) {

        Object[] args = joinPoint.getArgs();
        //joinPoint.getSignature().getDeclaringTypeName()类名
        //joinPoint.getSignature().getName() 获取到请求方法名
        logger.info(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName() +"结束。。。@AfterReturning:参数列表是"+ Arrays.asList(args)+"正常返回。。。@AfterReturning:运行结果:{" + result + "}");
    }

    /**
     * 记录异常
     * @param joinPoint
     * @param exception
     */
    @AfterThrowing(value = "pointCut()", throwing = "exception")
    public void logException(JoinPoint joinPoint, Exception exception) {
        Object[] args = joinPoint.getArgs();
        //joinPoint.getSignature().getDeclaringTypeName()类名
        //joinPoint.getSignature().getName() 获取到请求方法名
        logger.error(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName() +"结束。。。@AfterThrowing:参数列表是"+ Arrays.asList(args),exception);

    }

}

  • @Before
    此注解作用于方法执行之前,我们可以通过JoinPoint.getArgs()方法来获取到方法执行前接收到的参数。
  • @After
    此注解作用于方法执行之后
  • @AfterReturning
    此注解作用于方法返回之后,通过returning = "result"可以获取到方法的返回值
  • @AfterThrowing
    此注解作用于,异常抛出后
  • @Around
    环绕增强,这个会在后面仔细介绍。
    下面用代码来展示各个注解的执行顺序,测试类
/**
 * auhthor zy
 * date 20220518
 */
@RestController
public class TestController {

    private static final Log logger = LogFactory.getLog(TestController.class);

    @RequestMapping("getSxbm")
    public String getSxbm(HttpServletResponse response1, String jsonParam,String param) throws Exception {
        return "ok";
    }
}
20:22:17.330 [http-nio-8070-exec-2] INFO  com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestControllergetSxbm运行。。。@Before:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@78575c07, {"itemCode":"0103022000"}, null]
20:22:17.335 [http-nio-8070-exec-2] INFO  com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestController,getSxbm结束。。。@After:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@78575c07, {"itemCode":"0103022000"}, null]
20:22:17.335 [http-nio-8070-exec-2] INFO  com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestController,getSxbm结束。。。@AfterReturning:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@78575c07, {"itemCode":"0103022000"}, null]正常返回。。。@AfterReturning:运行结果:{ok}

执行顺序依次为Before,After,AfterReturning,可以看见这三个注解中均获取到了方法的参数,同时AfterReturning也获取到了方法的反参。已经达到了日志记录的功能。
下面测试这种写法的异常捕捉

20:50:38.573 [http-nio-8070-exec-2] INFO  org.springframework.web.servlet.DispatcherServlet - Initializing Servlet 'dispatcherServlet'
20:50:38.583 [http-nio-8070-exec-2] INFO  org.springframework.web.servlet.DispatcherServlet - Completed initialization in 10 ms
20:50:38.617 [http-nio-8070-exec-2] INFO  com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestControllergetSxbm运行。。。@Before:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@3649c92, {"itemCode":"0103022000"}, null]
20:50:38.622 [http-nio-8070-exec-2] INFO  com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestController,getSxbm结束。。。@After:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@3649c92, {"itemCode":"0103022000"}, null]
20:50:38.622 [http-nio-8070-exec-2] ERROR com.zy.main.aspect.ControllerLogAspect - com.zy.main.controller.TestController,getSxbm结束。。。@AfterThrowing:参数列表是[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@3649c92, {"itemCode":"0103022000"}, null]
java.lang.ArithmeticException: / by zero

可以看见@AfterThrowing 成功捕捉到了这次异常。下面来讲还没用到的around。
around 环绕增强,上面实现的所有日志记录功能都可以通过around来实现

around版日志记录实现
代码实现

/**
 * 控制日志aop记录类
 * author zy
 * create date 2022-02-16
 */
@Aspect
@Configuration
public class ControllerLogAspect {

    private static final Log logger = LogFactory.getLog(ControllerLogAspect.class);

    /**抽取公共的切入点表达式
     *
     */
    @Pointcut("execution(public String com.zy.main.controller.*.*(..))")
    public void pointCut() {

    }

    @Around("com.zy.main.aspect.ControllerLogAspect.pointCut()")
    public Object logEnd(ProceedingJoinPoint joinPoint) {
        Object[] args = joinPoint.getArgs();
        logger.info("入参为"+Arrays.asList(args));
        Object result = new Object();
        //  logger.info(joinPoint.getSignature().getDeclaringTypeName()+","+joinPoint.getSignature().getName() +"参数列表是"+ Arrays.asList(args));
        try{
             result = joinPoint.proceed();
            logger.info("反参为"+result.toString());

        }catch (Exception e){
            logger.error("Exception",e);
        } catch (Throwable throwable) {
            logger.error("throwable",throwable);
        }
        return result.toString();
    }

}

照例还是通过代码测试返回结果

21:53:14.864 [http-nio-8070-exec-2] INFO  com.zy.main.aspect.ControllerLogAspect - 入参为[com.alibaba.druid.support.http.WebStatFilter$StatHttpServletResponseWrapper@1e42dee5, {"itemCode":"0103022000"}, null]
21:53:14.868 [http-nio-8070-exec-2] INFO  com.zy.main.aspect.ControllerLogAspect - 反参为ok

可以看见,around同样也可以不修改业务方法但完成业务日志的记录。细心的同学可以注意到,After等注解使用的对象为JoinPoint,而我在around注解中使用的则是ProceedingJoinPoint。
从两者的源码来说前者是后者的父类
在这里插入图片描述
ProceedingJoinPoint类中,包含两个新方法
proceed()与proceed(object)方法,proceed这个方法的作用是通知目标方法执行,同时返回结果值。前者是不更改原始传参,后者是重写请求传入的参数,再传入方法中进行执行。
本次只是用作日志功能,所以不对原始参数进行重写。
从执行流程下around的执行流程,可以涵盖before,after,afterthrowing等注解的功能
在这里插入图片描述
从开发角度来说,AOP可以做到不修改原有的业务代码就可以增加通用的功能。而从AOP里面的注解来说,个人推荐使用around。但具体情况具体分析,大家根据自己的情况进行使用。下篇带大家了解自定义注解结合AOP的使用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

秋分渔火

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值