Spring AOP 同类调用失效问题及解决方案

在这里插入图片描述
首先我们看一下Spring Aop的原理图
在Spring中经常使用自定义注解或是spring已经封装
好的注解,通过AOP的方式是实现代码复用,避免重复劳动。而Spring实现AOP是通过动态代理来实现的(默认有接口的情况下使用JDK的动态代理,也可以通过配置proxyTargetClass来制定使用CGLib,没有接口的情况下使用CGLib).
但是无论哪一种代理,都是在原有方法的外面包一层,通过方法外的代理层来实现AOP的逻辑。
也就是说我们首先调用的是AOP代理对象而不是目标对象。但是我们使用this.getInstalledApk()时,this表示的是当前对象,而不是代理对象,因此注解失效。
所以,解决代理无效的根本方法就是获取到代理对象,使用代理对象进行调用。

常用的方法有三种
方法一:
最简单的方法就是将调用的方法getInstalledApk()方法放在另外一个类中进行调用,而不是在一个类中同类调用,就不会出现问题了。

方法二:
从ApplicationContext中直接获取
获取ApplicationContext有多种方法:
方法2.1 使用AopContext.currentProxy()

public class A {
    public void serviceB() {
            ......
            //此处调用的就是代理后的方法
            ((A)AopContext.currentProxy()).serviceA();
            ......
    }
}

使用AopContext.currentProxy()注意必须在程序启动时开启EnableAspectJAutoProxy注解,设置代理暴露方式为true,如下面所示:

/**
 * EnableAspectJAutoProxy注解两个参数作用分别为:
 *
 * 一个是控制aop的具体实现方式,为true的话使用cglib,为false的话使用java的Proxy,默认为false。
 * 第二个参数控制代理的暴露方式,解决内部调用不能使用代理的场景,默认为false。
 *
 *
 */
@EnableAspectJAutoProxy(proxyTargetClass = true, exposeProxy = true)
@SpringBootApplication
public class SpringAopApplication {

	public static void main(String[] args) {
		SpringApplication.run(SpringAopApplication.class, args);
	}
}

方法2.2 使用ApplicationContextAware
通过spring生命周期,直接将ApplicationContext注入进来

public class A implements ApplicationContextAware {
private ApplicationContext applicationContext;
    
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }
    
    
    public void serviceB() {
            ......
            //此处调用的就是代理后的方法
            applicationContext.getBean(A.class).serviceA();
            ......
    }
}

方法三:
在实现类中注入自身,这个有点取巧。还是以上面serviceB()为例,我们如果调用this.serviceA(),this指向对象本身、不会指向代理后的对象,因此肯定不可以,但我们可以让spring提供给我们代理后的对象:

@Service
public class Child implements IChild {
    
    //通过spring将代理后对象注入到self变量
    @Autowired
    private Child child;
    
    @Override
    public Map eat() throws GendorException {
        String name = child.name();//此处调用的就是代理后的方法
        System.out.println(name);
        return null;
    }

    @SysLog
    public String name (){
        return "zhangjiguo";
    }
}

总结:
(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 代理都无法织入增强实现。

  • 9
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring AOP可以使用AspectJ注解来实现接口重复调用的拦截。假设我们有一个接口`UserService`,其中有一个`saveUser`方法,我们希望在该方法被调用时进行拦截,防止重复调用。我们可以定义一个切面类`DuplicateInvokeAspect`,并为其添加一个`@Around`注解的方法,该方法将拦截`UserService`的`saveUser`方法,并在该方法中添加重复调用判断逻辑。 ``` @Aspect public class DuplicateInvokeAspect { private Map<String, Long> lastInvokeMap = new ConcurrentHashMap<>(); @Around("execution(* com.example.UserService.saveUser(..))") public Object checkDuplicateInvoke(ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().getName(); if (lastInvokeMap.containsKey(methodName)) { long lastInvokeTime = lastInvokeMap.get(methodName); long currentTime = System.currentTimeMillis(); if (currentTime - lastInvokeTime < 5000) { // 限制5秒内不能重复调用 throw new RuntimeException("不能重复调用" + methodName); } } lastInvokeMap.put(methodName, System.currentTimeMillis()); return joinPoint.proceed(); } } ``` 在上述代码中,我们使用`ConcurrentHashMap`来存储方法名和上一次调用时间的映射关系。在`checkDuplicateInvoke`方法中,我们首先判断该方法是否已经在`lastInvokeMap`中存在,如果存在则判断距离上一次调用的时间是否已经超过了5秒,如果没有超过则抛出异常,否则将当前时间更新为上一次调用时间,并继续执行原有的方法逻辑。 最后,我们需要在Spring配置文件中启用该切面类: ``` <aop:aspectj-autoproxy/> <bean id="duplicateInvokeAspect" class="com.example.DuplicateInvokeAspect"/> ``` 这样就完成了接口重复调用的拦截功能。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值