Spring同一个类中注解方法互相调用的问题

    在使用Spring时,很多初学者不了解Spring对象注入的机制和面向切面编程的原理,很容易犯一些错误。下面就是初学者最容易犯的错误。举例如下:

@Component

public class TestClass {
    private Random random = new Random();
    @Cacheable("cache1")
    public String getData(String key){
        System.out.println("load data from file, key = " + key);
        return  key + key;
    }
    @Scheduled(fixedDelay = 100)
    public void task(){
        int rand = random.nextInt(100);
        String strParam = String.format("%d", rand);
        String str = getData(strParam);
        System.out.println("result is " + str);
    }
}

    这里TestClass类里同时启用了Scheduled注解和@Cacheable注解。如果在外面其他类中调用getData(...)时,缓存机制会生效,但是在task函数内部调用getData(...)时,缓存机制不会生效。网上有人把这归纳为:不能在同一个类中互相调用注解过的方法,否则注解失效。

    为什么会有这样的差别呢?背后的原因就是Spring的对象注入机制。

    当外部通过@Autowired注解得到一个TestClass对象时,其实得到的是一个Spring包装过的代理对象。如下图所示。 

    当调用Obj.getData时,实际调用的是Spring的Proxy对象中的getData方法,该方法内置了Cache机制,在Cache检查后就会调用实际的TestClass对象中的getData方法。

    同理通过@Scheduled注解表示这是一个任务调度时,Spring Proxy对象中会初始化对应的调度线程池等工作,当触发调度条件时,再调用实际的TestClass对象。

    但是当在实际的TestClass对象中再调用getData时,不会触发Cache机制,因为此时不是调用的SpringCacheProxy对象,而是一个实际的TestClass对象,所以不会触发Cache机制。

    如果大家深入去读Spring完成注入和AOP编程实现的原理,可以发现动态代理是很重要的一个技术。目前Spring的动态代理主要是通过CGLib来实现的。后面我会再写CGLib的实现思路。

    那么我们该如何避免出现上述问题呢?

    首先我们应该牢记一个原则:同一个类中的注解方法互相调用时,注解机制可能是无效的。

    对于上述示例,我们可以把其拆分为两个类来实现,一个类完成任务调度、一个类完成Cache机制。这样在任务调度中调用的是Spring实现了Cache机制的代理类,可以确保其Cache机制生效。

原文地址:https://www.cnblogs.com/shuzl/p/5304762.html