Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Discovering annotated methods

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:

  1. Is there a better way to get to the annotations defined by the proxied class?
  2. Can you suggest any other way to discover all uses of @Cacheable in my project? I'd love to do without a marker interface.

Thanks!

like image 719
torngat Avatar asked Oct 26 '15 23:10

torngat


1 Answers

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 SmartInitializingSingletons 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.
        }
    }
}
like image 127
M. Deinum Avatar answered Nov 02 '22 12:11

M. Deinum