Search code examples
javaspringspring-el

Spring/SPeL: condition specific Cache control from one class to another


tl;dr;

I am looking for a way to set a "condition" attribute on a Spring Cacheable annotation from another class. Is there such a way?

Using Spring Cache where it should cache ONLY whenever a certain method has been invoked. This method is in ClassA, the method (data) to cache is in ClassB. What I want to do is something like this:

public ClassA implements myInterface {
...  
    private Boolean inProcess = false;

    public void cacheWhenThisMethodCalled() {
        try {
            inProcess = true;
            // do work here, somewhere along the line
            // the method in ClassB gets called
        } finally {
            inProcess = false;
        }
 }

ClassB:

public ClassB {
...
    @Cacheable(cacheNames={"aCache"}, condition="#classA.inProcess")
    public ValueClass findValueClass(UUID id)

However, I can't find the right condition for the SPeL to work. I have tried many combinations, none successfully. ClassA is a SpringBean, but the @Bean annotation returns the Interface, not the class. Can this be made to work? Or is there a better way?


Solution

  • Use a ThreadLocal - you would need to do that anyway for thread safety - otherwise a different thread can change the field.

    This works fine...

    @SpringBootApplication
    @EnableCaching
    public class So47580936Application {
    
        public static void main(String[] args) {
            SpringApplication.run(So47580936Application.class, args);
        }
    
        @Bean
        public ApplicationRunner runner(Bar bar) {
            return args -> {
                bar.cacheFromHere();
                bar.dontCacheFromHere();
            };
        }
    
        @Component
        public static class Foo {
    
            @Cacheable(cacheNames = "foos", condition = "T(com.example.So47580936Application$Bar).cacheit()")
            public String foo() {
                System.out.println("here");
                return "foo";
            }
    
        }
    
        @Component
        public static class Bar {
    
            private static final ThreadLocal<Boolean> cacheit = new ThreadLocal<>();
    
            @Autowired
            private Foo foo;
    
            public static boolean cacheit() {
                return cacheit.get() == null ? false : cacheit.get();
            }
    
            public void cacheFromHere() {
                try {
                    this.cacheit.set(true);
                    System.out.println("Cache:" + this.foo.foo());
                    System.out.println("Cache:" + this.foo.foo());
                }
                finally {
                    this.cacheit.remove();
                }
            }
    
            public void dontCacheFromHere() {
                System.out.println("Don't:" + this.foo.foo());
                System.out.println("Don't:" + this.foo.foo());
            }
    
        }
    
    }
    

    result:

    here
    Cache:foo
    Cache:foo
    here
    Don't:foo
    here
    Don't:foo
    

    EDIT

    Or, you can just make the ThreadLocal a @Bean ...

    @SpringBootApplication
    @EnableCaching
    public class So47580936Application {
    
        public static void main(String[] args) {
            SpringApplication.run(So47580936Application.class, args);
        }
    
        @Bean
        public ApplicationRunner runner(Bar bar) {
            return args -> {
                bar.cacheFromHere();
                bar.dontCacheFromHere();
            };
        }
    
        @Bean
        public ThreadLocal<Boolean> cacheit() {
            return new ThreadLocal<>();
        }
    
        @Component
        public static class Foo {
    
            @Cacheable(cacheNames = "foos", condition = "@cacheit.get() ?: false")
            public String foo() {
                System.out.println("here");
                return "foo";
            }
    
        }
    
        @Component
        public static class Bar {
    
            @Autowired
            private ThreadLocal<Boolean> cacheit;
    
            @Autowired
            private Foo foo;
    
            public void cacheFromHere() {
                try {
                    this.cacheit.set(true);
                    System.out.println("Cache:" + this.foo.foo());
                    System.out.println("Cache:" + this.foo.foo());
                }
                finally {
                    this.cacheit.remove();
                }
            }
    
            public void dontCacheFromHere() {
                System.out.println("Don't:" + this.foo.foo());
                System.out.println("Don't:" + this.foo.foo());
            }
    
        }
    
    }