In my Spring application, I have components that use Spring's caching mechanism. Each @Cacheable
annotation specifies the cache that is to be used. I'd like to autodiscover all the caches that are needed at startup so that they can be automatically configured.
The simplest approach seemed to create a marker interface (ex: CacheUser
) to be used by each caching component:
@Component
public class ComponentA implements CacheUser {
@Cacheable("dictionaryCache")
public String getDefinition(String word) {
...
}
}
I would then have Spring autodiscover all the implementations of this interface and autowire them to a configuration list that can be used when configuring the cache manager(s). This works.
@Autowired
private Optional<List<CacheUser>> cacheUsers;
My plan was to take each discovered class and find all methods annotated with @Cacheable
. From there I would access the annotation's properties and obtain the cache name. I'm using AnnotationUtils.findAnnotation()
to get the annotation declaration.
That's where the plan falls apart. Spring actually wires proxies instead of the raw component, and the annotations aren't copied over to the proxies' methods. The only workaround I've found exploits the fact that the proxy implements Advised
which provides access to the proxied class:
((Advised)proxy).getTargetSource().getTargetClass().getMethods()
From there I can get the original annotations, but this approach is clearly brittle.
So two questions, really:
@Cacheable
in my project? I'd love to do without a marker interface.Thanks!
Spring has a lot of infrastructure interfaces which can help you tap into the lifecycle of the container and/or beans. For your purpose you want to use a BeanPostProcessor
and the SmartInitializingSingleton
.
The BeanPostProcessor
will get a callback for all the beans constructed, you will only need to implement the the postProcessAfterInitialization
method. You can in that method detect the annotations and fill a list of caches.
Then in the SmartInitializingSingleton
s afterSingletonsInstantiated
method you use this list to bootstrap/init your caches.
Something like the following (it is untested but should give you an idea).
public class CacheInitialingProcessor implements BeanPostProcessor, SmartInitializingSingleton {
private final Set<String> caches = new HashSet<String>();
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
Class<?> targetClass = AopUtils.getTargetClass(bean);
ReflectionUtils.doWithMethods(targetClass, new ReflectionUtils.MethodCallback() {
@Override
public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException {
Cacheable cacheable = AnnotationUtils.getAnnotation(method, Cacheable.class);
if (cacheable != null) {
caches.addAll(Arrays.asList(cacheable.cacheNames()));
}
}
});
return bean;
}
@Override
public void afterSingletonsInstantiated() {
for (String cache : caches) {
// inti caches.
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With