记一次AOP踩的坑(注解方式AOP不生效) 置顶!

2020-09-14

最近接了一个需求,使用配置的优惠券/项目卡生成一批券码.用户在H5端,自己填手机号绑定券码.然后后台将券码对应的优惠券/项目卡发放到用户的账户中.发放成功短信通知用户.

发送短信的方式,我采用的是基于注解的AOP 后置通知 .具体代码如下

import java.lang.annotation.*;

/**
 * @author gaotianyi
 * @date 2019/9/10 3:40 下午
 */
@Target({ElementType.PARAMETER,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SendSms {
}

具体的AOP


/**
 * @author gaotianyi
 * @date 2019/9/10 3:35 下午
 */
@Slf4j
@Aspect
@Component
public class SendSmsConfig {

    @Autowired
    private RemoteSMSService remoteSMSService;


    @Pointcut("@annotation(com.myour.marketing.core.anno.SendSms)")
    public void pointCutReturn() {

    }

    /**
     * 切面
     *
     * @param
     * @return
     * @throws Throwable
     */
    @AfterReturning(value = "pointCutReturn()",returning = "param")
    public void methodHandler(Object param) {
        SendSMSParam param1 = (SendSMSParam) param;
        String phoneNumbers = param1.getPhoneNumbers();
        if (StringUtils.isNotBlank(phoneNumbers)) {
            this.sendMessage(param1);
        } else {
            log.info(">>>>>>>>>>>邀请人没有手机号,不发送短信通知  :{}<<<<<<<<<<<<<<", param);
        }
    }


    /**
     * 发送短信
     *
     * @param param
     */
    public void sendMessage(SendSMSParam param) {
// code
}

调用的地方伪代码

public interface A {

String  A(C c );

SendSMSParam B( . .);

}

我在 A方法中调用了B方法 ,B方法上注解了 @SendSms注解被定义成了一个切点 .但是测试的时候成功领取券码,却没有收到短信通知 .于是debug查看,发现该方法注解未生效 .检查了 Pointcut表达式没有问题,百思不得其姐,于是乎百度

失效原因 :

因为我们的AOP走了类中自调用 ,那么为什么自调用不会生效了?
在使用 Spring AOP 的时候,我们从 IOC 容器中获取的 Service Bean 对象其实都是代理对象,而不是那些 Service Bean 对象本身,也就是说获取的并不是被代理对象或代理目标当我在自己的 Service 类中使用 this 关键字嵌套调用同类中的其他方法时,由于 this 关键字引用的并不是该 Service Bean 对象的代理对象,而是其本身,故 Spring AOP 是不能拦截到这些被嵌套调用的方法的

我们知道aop都是经过动态代理来实现的,而动态代理管理的范围也一定是spring容器中的对象所调用的方法,即autowired进来的对象调用方法时,实际调用的是代理方法,然后代理方法里面会调用该类自身方法
针对this直接调用的方法会直接调用该代理对象的方法,这个this就是该类直接调用自己方法,所以不会在经过aop

解决办法

就是将被注解的那个方法放在其他类中进行(最推崇的方式)

总结

(1).在一个类内部调用时,被调用方法的 AOP 声明将不起作用。Spring 事务管理注解 @Transactional 也一样。

(2).对于基于接口动态代理的 AOP 事务增强来说,由于接口的方法都必然是 public 的,这就要求实现类的实现方法也必须是 public 的(不能是 protected、private 等),同时不能使用 static 的修饰符。所以,可以实施接口动态代理的方法只能是使用 public 或 public final 修饰符的方法,其他方法不可能被动态代理,相应的也就不能实施 AOP 增强,换句话说,即不能进行 Spring 事务增强了。

(3).基于 CGLib 字节码动态代理的方案是通过扩展被增强类,动态创建其子类的方式进行 AOP 增强植入的。由于使用 final、static、private 修饰符的方法都不能被子类覆盖,相应的,这些方法将无法实施 AOP 增强。所以方法签名必须特别注意这些修饰符的使用,以免使方法不小心成为事务管理的漏网之鱼。

(4).上面我的例子的方法符合上述条件,但注解仍然失效,主要原因是在于同一类中的方法互相调用,调用者指向当前对象,所以无论是接口代理还是 cglib 代理都无法织入增强实现。

谢谢以下几位大佬给的参考

探究Spring AOP 同类调用失效问题
aop三个坑:失效、注入对象为空、方法固定返回null


标题:记一次AOP踩的坑(注解方式AOP不生效)
作者:yifabukeshoushi
地址:https://blog.tyxgg.cn/articles/2019/12/03/1575337704737.html