JoeG
JoeG

Reputation: 7642

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?

Upvotes: 0

Views: 1265

Answers (1)

Gary Russell
Gary Russell

Reputation: 174544

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());
        }

    }

}

Upvotes: 4

Related Questions