@Around踩坑记录

@Around踩坑记录

先上结论:

如果你在定义切面的时候打算使用环绕通知➕定义注解的方式,那么在进行*@Around*("(@annotation(costTrace))") 类似这样的定义的时候,”costTrace“一定要与你定义切面中入参的参数名字一致,而不是参数类型(你定义的注解名字)一致.

1. 背景

在完善自己暑期实习项目的过程中,发现自己在业务逻辑中花了大量的篇幅去打一些和方法耗时计算相关的日志,于是在优化的过程中,第一步便是利用切面编程的思想将这些计算耗时的日志代码通过切面的方式,悄无声息的侵入,把代码篇幅都留给业务逻辑,而不是这些千篇一律的日志打印。

在这里,我通过传统的 (自定义注解 ➕ spring AOP 切面)的方式进行,以下是我的初始版本⬇️

1.1 引入依赖与定义配置


<dependency> 
  <groupId>org.aspectj</groupId>  
  <artifactId>aspectjrt</artifactId> 
</dependency>  
<dependency> 
  <groupId>org.aspectj</groupId>  
  <artifactId>aspectjweaver</artifactId> 
</dependency>

spring的xml配置文件中定义:

<aop:config proxy-target-class="true"/>
<aop:aspectj-autoproxy/>

1.2 自定义注解

/**
 * @author kaihua
 * @describe : 被该注解标记,将被进行代价计算与日志打印
 * @date 2023/7/4
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD })
public @interface CostTrace {

    String value() default "";

}

1.3 定义切面

/**
 * @author kaihua
 * @describe
 * @date 2023/7/4
 */
@Aspect
@Component
public class CostTraceAspect {

    /**
     * CostTrace 切面
     * @param joinPoint : 切点
     * @param costTrace : 方法注解
     * @return : 返回值
     */
    @Around("(@annotation(CostTrace))")
    public Object handleJoinPoint(ProceedingJoinPoint joinPoint, CostTrace costTrace) {
        //1. 获取切入点方法信息
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();

        String className = methodSignature.getDeclaringType().getName();
        String methodName = methodSignature.getName();
        String costTraceInformation = costTrace.value();
        Object[] args = joinPoint.getArgs();
        String requestStr = JSON.toJSONString(args);

        //2. 定义方法开始时间
        long begin = System.currentTimeMillis();
        Object result = null;

        try {
            //3. 执行方法
            result = joinPoint.proceed(args);

            //4.1 方法执行完毕之后打印日志
            LoggerManager.info(
                    String.format(
                            "the method %s of class %s execution successful, the input parameters are %s, the total time consumption is %dms, the other information: %s"
                            , methodName
                            , className
                            , requestStr
                            , System.currentTimeMillis() - begin
                            , costTraceInformation)
                    ,""
                    ,"100");
        } catch (Throwable e) {
            e.printStackTrace();
            //4.2 方法若执行失败打印日志
            LoggerManager.error(
                    String.format(
                            "the method %s of class %s execution error, the input parameters are %s, the other information: %s"
                            , methodName
                            , className
                            , requestStr
                            , costTraceInformation)
                    ,""
                    ,e);
        }

        return result;
    }
}

2. 问题的出现

在预发环境部署的过程中出现了如下报错

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'dependencyTypeHolder' defined in URL [jar:file:/home/admin/taefileserver/target/taefileserver.war/WEB-INF/lib/ateye-client-2.3.8.jar!/ateye-scene.xml]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.config.internalTransactionAdvisor': Cannot resolve reference to bean 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0' while setting bean property 'transactionAttributeSource'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.transaction.annotation.AnnotationTransactionAttributeSource#0': BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#0': Cannot resolve reference to bean 'cursitor-pointcut' while setting bean property 'pointcut'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'cursitor-pointcut' defined in class path resource [spring-cursitor.xml]: BeanPostProcessor before instantiation of bean failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.aop.support.DefaultBeanFactoryPointcutAdvisor#1': Cannot resolve reference to bean 'jmonitor-pointcut' while setting bean property 'pointcut'; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'jmonitor-pointcut' defined in class path resource [jmonitor-spring.xml]: Initialization of bean failed; nested exception is java.lang.IllegalArgumentException: error at ::0 formal unbound in pointcut

报错非常长,但是通过控制变量法(通过部署前后的日志变化比较,不推荐,,最笨的方法)以及读取日志内容(推荐,但是有时候难以排查问题)发现,日志中pointcut的字眼,并且报错的本质原因是bean容器初始化失败,那必定是某个bean的定义出现了问题。

结合自己新添加的代码,发现只有自己定义的切面被标注了@Component,便把问题锁定在切面中了,但是这段代码乍一看,并没有什么错误,就是一个非常简单的环绕通知定义。

3. 问题的排查

通过复制日志报错搜索解决方案显然不太可行,后面专门去搜索了@Around配合注解如何去进行定义,终于在廖雪峰老师的网站看到了答案.

👉 https://www.liaoxuefeng.com/wiki/1252599548343744/1310052317134882

请添加图片描述

我又重新尝试修改了自己定义的切面的代码,进行部署,终于成功了!!

但是我这里还是不明白,为什么不能够使用注解类型,非要去使用对应的注解参数名字呢?

在一番搜索和chatGpt battle大战之后,大概搞清楚了原因:

**“在使用 @annotation 拦截注解时,需要获取到该注解的属性值,而注解的属性值是在注解对象中定义的,而不是在注解类型中定义的。因此,在 @annotation 中传入注解对象,可以获取到注解的属性值。”**

4. 解决方法的证实

我将切面的@Around定义分别换为了两个版本,结果都可以部署成功,基本可以证明了这个做法的正确性。

// version 1
@Around("(@annotation(costTrace))")
public Object handleJoinPoint(ProceedingJoinPoint joinPoint, CostTrace costTrace)

// version 2
@Around("(@annotation(cos))")
public Object handleJoinPoint(ProceedingJoinPoint joinPoint, CostTrace cos)

5. 小结

虽然这个只是一个非常非常非常小的排查问题案例,但是还是不由得产生非常多的感悟:

“作为一个臭写代码的程序员,我们所谓的学习,只是在学习其他人定义的规则,最典型的就是开发过程中的各种框架,我们在不断的学习别人定下来的规则。虽然这个现实是绝望的,但是我们也要从中有所收获,我们今天了解了这个规则,并不应该仅仅停留于学习到了这个规则,更可贵的是要去尝试去思考规则制定者在制定规则时候的思考”

@Around是Spring AOP中的一个注解,用于实现环绕增强(Around Advice)。它可以在目标方法执行前后进行拦截,并且可以控制目标方法的执行。在使用@Around注解时,需要注意以下几点: 1. @Around注解的参数应该是一个方法签名,而不是runTime。这是因为@Around注解需要指定一个切入点表达式,用于确定哪些方法需要被拦截。方法签名可以包含方法名、参数类型和返回类型等信息,以便准确地匹配目标方法。 2. @Around注解可以携带参数,这取决于自定义的注解中是否携带参数。如果自定义的注解中携带了参数,那么在增强处理类中也可以使用这些参数。通过在@Around注解中使用参数,可以在拦截方法执行前后对参数进行处理或传递额外的参数给目标方法。 下面是一个示例代码,演示了@Around注解的使用方法: ```java @Aspect @Component public class LoggingAspect { @Around("execution(* com.example.MyService.*(..))") public Object aroundAdvice(ProceedingJoinPoint joinPoint) throws Throwable { System.out.println("Before method execution"); Object result = joinPoint.proceed(); System.out.println("After method execution"); return result; } } ``` 在上面的代码中,@Around注解被用于定义一个环绕增强的方法aroundAdvice。该方法会在目标方法执行前输出"Before method execution",在目标方法执行后输出"After method execution"。通过调用joinPoint.proceed()方法,可以继续执行目标方法。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值