0
\$\begingroup\$

I would like to enable application behaviour based on the presence of a custom annotation that marks the configuration class (normally named something like ApplicationConfig.class) in a Spring 4 application.

This feature functions in the same way as do @EnableCache, @EnableRepositories etc, in the sense that adding these annotations adds behaviour to the application.

The following code shows the way I have decided to implement the feature. It detects and analyses the classes that are loaded by Spring at application startup and fires an event if it determines that one of those classes is annotated with the specified custom annotation. This code works as designed however the use of reflection feels clumsy and I would like to seek the advice of other developers who may have solved a similar problem in a more elegant/different manner.

The custom annotation class.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ServiceRegistration {
    boolean enabled() default true;
}

The configuration class.

@ServiceRegistration(enabled = true)
@Configuration
@ComponentScan(basePackageClasses = Application.class, excludeFilters = @Filter({Controller.class, Configuration.class}))
@EnableJpaRepositories("com.package.repository")
class ApplicationConfig { 

    @Bean
    public static PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
        PropertyPlaceholderConfigurer ppc = new PropertyPlaceholderConfigurer();
        ppc.setLocation(new ClassPathResource("/persistence.properties"));
        return ppc;
    }

}

The class that detects the presence of the annotation.

@Component
public class ServiceRegistrationEventListener {

    private static final String ANNOTATED_CLASSES = "annotatedClasses";

    @Autowired
    ApplicationEventPublisher applicationEventPublisher;

    @Autowired
    ApplicationContext applicationContext;

    @EventListener
    void contextRefreshedEvent(ContextRefreshedEvent event) throws NoSuchFieldException, IllegalAccessException {

        ApplicationContext applicationContext = event.getApplicationContext();
        AnnotationConfigWebApplicationContext annotationContext = ((AnnotationConfigWebApplicationContext) applicationContext);
        for (Field field : annotationContext.getClass().getDeclaredFields()) {
            if (ANNOTATED_CLASSES.equals(field.getName())) {
                field.setAccessible(true);
                Object aContext = field.get(annotationContext);
                if (aContext instanceof Set) {
                    Set<Class<?>> classes = (Set<Class<?>>) aContext;
                    for (Class clazz : classes) {
                        for (Annotation annotation : clazz.getDeclaredAnnotations()) {
                            if (annotation.annotationType().isAssignableFrom(ServiceRegistration.class)) {
                                applicationEventPublisher.publishEvent(new RegistrationEvent(((ServiceRegistration) annotation).enabled()));
                            }
                        }
                    }
                }
            }
        }
    }


    class RegistrationEvent {

        private boolean enabled;

        public RegistrationEvent(boolean enabled) {
            this.enabled = enabled;
        }

        public boolean enabled() {
            return enabled;
        }

    }

}

Values set in the annotation e.g. @ServiceRegistration(enabled = true) are recovered and passed into the published event.

Any ideas on how to improve this code and especially ideas on completely different ways to achieve the same are welcome.

\$\endgroup\$
-2
\$\begingroup\$

I'm not that familiar with Spring, but if your goal is to get all types annotated with FooBar and then the enabled attribute, you may just use the Reflections library. When your application is bootstrapping you would:

Reflections reflections = new Reflections("my.project");

And in your listener:

@EventListener
void contextRefreshedEvent(ContextRefreshedEvent event) throws NoSuchFieldException, IllegalAccessException {
    Set<Class<?>> classes = reflections.getTypesAnnotatedWith(FooBar.class);
    for (Class clazz : classes) {
        boolean enabled = clazz.getAnnotation(FooBar.class).enabled();
        System.out.println("Enabled: " + enabled);
    }
}

Notice that you don't need to get all annotations in the class, check types and then cast if you use getAnnotation. The advantage of using Reflections is that class metadata is indexed, which means the expensive reflection operations aren't done all the time. It won't work, however, if Spring changes annotations on runtime (which I don't know if it does or not). If you can't use the Reflections library, just the getAnnotation method is already an improvement over your original for over each annotation.

| improve this answer | |
\$\endgroup\$
  • 1
    \$\begingroup\$ You should not be guessing at what the OP's code is intended to do and try to resolve that problem. The point of CodeReview is to know what the code achieves and find ways to do the same thing in a cleaner or faster or generally better way. Your answer may (and I assume does) completely miss the original purpose of the code \$\endgroup\$ – Vogel612 Jan 18 '16 at 14:39
  • \$\begingroup\$ That's exactly what my answer does, it shows a cleaner and faster way to detect if a class is annotated with a custom annotation. The annotation mechanism is a Java feature, it doesn't matter if you're using Spring or not. But if your point is that I'm assuming Spring does not change annotations on runtime, no problem. The use of getAnnotation is already much better than the OP's code and I'll edit my answer to show an option without the Reflections library. \$\endgroup\$ – andrepnh Jan 18 '16 at 15:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.