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

Spring AOP 同类调用失效问题及解决方案_第1张图片
首先我们看一下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 代理都无法织入增强实现。

你可能感兴趣的:(Java,spring,java)